socialmux 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +220 -0
  7. data/Rakefile +1 -0
  8. data/lib/socialmux/auth_mapper/base.rb +25 -0
  9. data/lib/socialmux/auth_mapper/facebook.rb +7 -0
  10. data/lib/socialmux/auth_mapper/github.rb +7 -0
  11. data/lib/socialmux/auth_mapper/google_oauth2.rb +7 -0
  12. data/lib/socialmux/auth_mapper/guess_name.rb +22 -0
  13. data/lib/socialmux/auth_mapper/linkedin.rb +7 -0
  14. data/lib/socialmux/auth_mapper/twitter.rb +7 -0
  15. data/lib/socialmux/auth_mapper.rb +26 -0
  16. data/lib/socialmux/event.rb +13 -0
  17. data/lib/socialmux/result.rb +15 -0
  18. data/lib/socialmux/strategy.rb +69 -0
  19. data/lib/socialmux/version.rb +4 -0
  20. data/lib/socialmux.rb +9 -0
  21. data/socialmux.gemspec +31 -0
  22. data/spec/fixtures/facebook_response.json +158 -0
  23. data/spec/fixtures/github_response.json +65 -0
  24. data/spec/fixtures/google_response.json +35 -0
  25. data/spec/fixtures/linkedin_response.json +45 -0
  26. data/spec/fixtures/twitter_response.json +239 -0
  27. data/spec/lib/.gitkeep +0 -0
  28. data/spec/lib/auth_mapper/facebook_spec.rb +18 -0
  29. data/spec/lib/auth_mapper/github_spec.rb +18 -0
  30. data/spec/lib/auth_mapper/google_oauth2_spec.rb +19 -0
  31. data/spec/lib/auth_mapper/linkedin_spec.rb +20 -0
  32. data/spec/lib/auth_mapper/twitter_spec.rb +22 -0
  33. data/spec/lib/result_spec.rb +17 -0
  34. data/spec/lib/strategy_spec.rb +112 -0
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/support/fixture_helpers.rb +13 -0
  37. data/spec/support/rspec_config.rb +11 -0
  38. metadata +210 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4feb9f255148e3abc9a97d01afe6a156bbfa80e8
4
+ data.tar.gz: fb8b815274c1aaee7ead7c86a9132ca6c1c80cb1
5
+ SHA512:
6
+ metadata.gz: 7768d1a811be1d8f5dd570e74692ef26e3f97d3a3c40467f5ff103a92c800b545388c83cfe0425df3bfbcdf166d28aa3b9e633ee1a006270e55c3f4fd16b7c7d
7
+ data.tar.gz: d064285e28aaeb95718e11a38ca81609d22433e3cfe9042b80568c73826334b4ec864420ccdd37a498ae87239d0274944d6d232581a810ff9e1fa75925d8f5d5
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+
7
+ script: "bundle exec rspec spec"
8
+
9
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in socialmux.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stefano Verna
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # Socialmux
2
+
3
+ [![Build Status](https://travis-ci.org/stefanoverna/socialmux.png)](https://travis-ci.org/stefanoverna/socialmux)
4
+ [![Coverage Status](https://coveralls.io/repos/stefanoverna/socialmux/badge.png?branch=master)](https://coveralls.io/r/stefanoverna/socialmux?branch=master)
5
+
6
+ Socialmux implements a strategy to add multiple social providers to the same
7
+ user. It's meant to be used togheter with OmniAuth.
8
+
9
+ * Socialmux works with any DB schema and ORM, as it delegates DB management to
10
+ a simple adapter class the developer needs to implement.
11
+
12
+ * Socialmux works with any authentication system, as it just prepares the User
13
+ model, and leave the authentication part to your controllers.
14
+
15
+ ## The strategy
16
+
17
+ * If a matching social account is already present in the database:
18
+ * it returns the linked user and no change is made;
19
+
20
+ * If a user is already signed in:
21
+ * it fills up any user blank attribute with data coming from the
22
+ authentication;
23
+ * it builds a new social authentication model, and adds it to the user model;
24
+ * it returns the modified user without saving it;
25
+
26
+ * If an user exists with the same email:
27
+ * it fills up any user blank attribute with data coming from the
28
+ authentication;
29
+ * it builds a new social authentication model, and adds it to the user model;
30
+ * it returns the modified user without saving it;
31
+
32
+ * Otherwise a new User instance gets instantiated:
33
+ * it fills up any user blank attribute with data coming from the
34
+ authentication;
35
+ * it builds a new social authentication model, and adds it to the user model;
36
+ * it returns the modified user without saving it;
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's Gemfile:
41
+
42
+ gem 'socialmux'
43
+
44
+ And then execute:
45
+
46
+ $ bundle
47
+
48
+ Or install it yourself as:
49
+
50
+ $ gem install socialmux
51
+
52
+ ## Typical Usage
53
+
54
+ This is a real word example for the following schema:
55
+
56
+ * `User (email, password)`
57
+ * `Profile (user_id, first_name, last_name)`
58
+ * `SocialAuth (user_id, provider, uid)`
59
+
60
+ User requires an email, so if an external auth provider does not give us this
61
+ information, a form will be shown for the user to fill it in.
62
+
63
+ ### `config/routes.rb`
64
+
65
+ ```ruby
66
+ MyApp::Application.routes.draw do
67
+ devise_for :users
68
+
69
+ # OmniAuth route
70
+ match '/auth/:provider/callback' => 'social#callback', via: [:get, :post]
71
+
72
+ # This is where the magic happens :)
73
+ match '/auth/complete' => 'social#complete', as: :social_complete, via: [:get, :post], as: :social_complete
74
+
75
+ # ...
76
+ end
77
+ ```
78
+
79
+ ### `app/services/social_schema_adapter.rb`
80
+
81
+ This is the adapter to the database. Socialmux requires these methods'
82
+ interfaces. The implementation will differ based on the project.
83
+
84
+ ```ruby
85
+ class SocialSchemaAdapter
86
+ def init_user
87
+ User.new
88
+ end
89
+
90
+ def find_user_with_email(email)
91
+ User.where(email: email).first
92
+ end
93
+
94
+ def find_user_with_authentication(data)
95
+ auth = SocialAuth.where(provider: data.provider, uid: data.uid).first
96
+ auth.user if auth.present?
97
+ end
98
+
99
+ def update_user_with_params(user, user_params)
100
+ user.attributes = user_params
101
+ end
102
+
103
+ def update_user_with_data_if_blank(user, data)
104
+ profile = user.profile || user.build_profile
105
+
106
+ update_attr_if_blank(user, :email, data.email)
107
+ update_attr_if_blank(profile, :first_name, data.first_name)
108
+ update_attr_if_blank(profile, :last_name, data.last_name)
109
+ end
110
+
111
+ def build_authentication(user, data)
112
+ auth_attrs = { provider: data.provider, uid: data.uid }
113
+ user.social_auths.build(auth_attrs)
114
+ end
115
+
116
+ private
117
+
118
+ def update_attr_if_blank(record, key, value)
119
+ current_value = record.send(key)
120
+ record.send("#{key}=", value) if current_value.blank?
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### `app/controllers/social_controller.rb`
126
+
127
+ ```ruby
128
+ class SocialController < ApplicationController
129
+ def callback
130
+ session[:omniauth] ||= request.env['omniauth.auth']
131
+ redirect_to social_complete_path
132
+ end
133
+
134
+ def complete
135
+
136
+ # Here we instanciate our Socialmux strategy with all the required
137
+ # arguments
138
+
139
+ authentication = Socialmux::Strategy.new(
140
+ adapter: SocialSchemaAdapter.new,
141
+ current_user: current_user,
142
+ omniauth_data: session[:omniauth],
143
+ user_params: user_params
144
+ )
145
+
146
+ # `.result` returns a Socialmux::Result, which gives us the
147
+ # found/generated user that we need to sign in
148
+ result = authentication.result
149
+ @user = result.user
150
+
151
+ # If you cannot save the model, it's because some required fields
152
+ # could not be extracted from the thirdy party authentication source.
153
+ # We render a form that points to this very same action.
154
+
155
+ if @user.save
156
+ sign_in_and_notice(@user, result.event)
157
+ else
158
+ render :complete
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def user_params
165
+ if request.post?
166
+ params.require(:user).permit(:email, profile_attributes: [ :first_name, :last_name ])
167
+ else
168
+ {}
169
+ end
170
+ end
171
+
172
+ def sign_in_and_notice(user, event)
173
+ flash[:notice] = "Result: #{event}"
174
+ session.delete(:omniauth)
175
+ sign_in_and_redirect(user)
176
+ end
177
+ end
178
+ ```
179
+
180
+ ### `app/views/social/complete.html.slim`
181
+
182
+ ```slim
183
+ h1 Sign up with Social
184
+
185
+ = simple_form_for @user, url: social_complete_path do |f|
186
+
187
+ p You're close to finish the registration process... In order to be able to restore your account in the future, please insert your email!
188
+
189
+ = f.input :email
190
+
191
+ fieldset
192
+ legend Optional data
193
+
194
+ = f.simple_fields_for :profile do |p|
195
+ = p.input :first_name
196
+ = p.input :last_name
197
+
198
+ = f.button :submit
199
+ ```
200
+
201
+ ## About sessions
202
+
203
+ In the above example, OmniAuth `AuthResult` is stored in session until the user
204
+ completes the sign up process.
205
+
206
+ If you use cookies, this won't work and raise a `CookieOverflow` error. Use a
207
+ session store backed by an Active Record class:
208
+
209
+ ```ruby
210
+ gem 'activerecord-session_store', github: 'rails/activerecord-session_store'
211
+ ```
212
+
213
+ ## Contributing
214
+
215
+ 1. Fork it
216
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
217
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
218
+ 4. Push to the branch (`git push origin my-new-feature`)
219
+ 5. Create new Pull Request
220
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ require 'hashie'
2
+ require 'active_support/core_ext/module/delegation'
3
+
4
+ module Socialmux
5
+ module AuthMapper
6
+ class Base
7
+ attr_reader :data
8
+
9
+ delegate :uid, to: :data
10
+ delegate :info, to: :data
11
+ delegate :provider, to: :data
12
+
13
+ delegate :first_name, to: :info
14
+ delegate :last_name, to: :info
15
+ delegate :email, to: :info
16
+ delegate :image, to: :info
17
+ delegate :description, to: :info
18
+
19
+ def initialize(data)
20
+ @data = Hashie::Mash.new(data)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,7 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class Facebook < Base
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class Github < GuessName
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class GoogleOauth2 < Base
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,22 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class GuessName < Base
4
+ def first_name
5
+ name_chunks.first
6
+ end
7
+
8
+ def last_name
9
+ last_name = name_chunks.dup
10
+ last_name.shift
11
+ last_name.join(" ")
12
+ end
13
+
14
+ private
15
+
16
+ def name_chunks
17
+ info.name.split(/\s+/)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,7 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class Linkedin < Base
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ module Socialmux
2
+ module AuthMapper
3
+ class Twitter < GuessName
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,26 @@
1
+ require 'hashie'
2
+
3
+ require 'socialmux/auth_mapper/base'
4
+ require 'socialmux/auth_mapper/guess_name'
5
+ require 'socialmux/auth_mapper/facebook'
6
+ require 'socialmux/auth_mapper/twitter'
7
+ require 'socialmux/auth_mapper/github'
8
+ require 'socialmux/auth_mapper/google_oauth2'
9
+ require 'socialmux/auth_mapper/linkedin'
10
+
11
+ module Socialmux
12
+ module AuthMapper
13
+ class NotFound < RuntimeError; end
14
+
15
+ def self.init_with_data(data)
16
+ data = Hashie::Mash.new(data)
17
+
18
+ klass_name = "Social::AuthMapper::#{data.provider.classify}"
19
+ klass = klass_name.constantize
20
+ klass.new(data)
21
+ rescue NameError
22
+ raise NotFound, "Mapper #{klass_name} for provider #{data.provider} not found!"
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,13 @@
1
+ module Socialmux
2
+ module Event
3
+ SIGN_IN = :sign_in
4
+ SIGN_UP = :sign_up
5
+ TOUCHED_CURRENT_USER = :updated_current_user
6
+ TOUCHED_EMAIL_USER = :updated_email_user
7
+
8
+ def self.all
9
+ [ SIGN_IN, SIGN_UP, TOUCHED_CURRENT_USER, TOUCHED_EMAIL_USER ]
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,15 @@
1
+ module Socialmux
2
+ class Result < Struct.new(:user, :event)
3
+
4
+ def method_missing(name, *args, &block)
5
+ Event.all.each do |event_name|
6
+ if name.to_s == "#{event_name}?"
7
+ return event == event_name
8
+ end
9
+ end
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,69 @@
1
+ require 'active_support/core_ext/hash/reverse_merge'
2
+ require 'active_support/core_ext/hash/keys'
3
+
4
+ module Socialmux
5
+ class Strategy
6
+ attr_reader :adapter
7
+ attr_reader :current_user
8
+ attr_reader :omniauth_data
9
+ attr_reader :user_params
10
+
11
+ def initialize(options)
12
+ options.assert_valid_keys(:adapter,
13
+ :current_user,
14
+ :omniauth_data,
15
+ :user_params)
16
+
17
+ options.each do |key, value|
18
+ instance_variable_set "@#{key}", value
19
+ end
20
+ end
21
+
22
+ def result
23
+ returning_user ||
24
+ augmented_current_user ||
25
+ augmented_user_with_same_email ||
26
+ new_user
27
+ end
28
+
29
+ private
30
+
31
+ def returning_user
32
+ user = adapter.find_user_with_authentication(data)
33
+ Result.new(user, Event::SIGN_IN) if user
34
+ end
35
+
36
+ def augmented_current_user
37
+ return nil if !current_user
38
+
39
+ augment_user(current_user)
40
+ Result.new(current_user, Event::TOUCHED_CURRENT_USER)
41
+ end
42
+
43
+ def augmented_user_with_same_email
44
+ user = adapter.find_user_with_email(data.email)
45
+ return if !user
46
+
47
+ augment_user(user)
48
+ return Result.new(user, Event::TOUCHED_EMAIL_USER)
49
+ end
50
+
51
+ def new_user
52
+ user = adapter.init_user
53
+ augment_user(user)
54
+ Result.new(user, Event::SIGN_UP)
55
+ end
56
+
57
+ def data
58
+ @data ||= AuthMapper.init_with_data(omniauth_data)
59
+ end
60
+
61
+ def augment_user(user)
62
+ adapter.update_user_with_data_if_blank(user, data)
63
+ adapter.update_user_with_params(user, user_params)
64
+ adapter.build_authentication(user, data)
65
+ user
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,4 @@
1
+ module Socialmux
2
+ VERSION = "0.0.1"
3
+ end
4
+
data/lib/socialmux.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'socialmux/version'
2
+ require 'socialmux/event'
3
+ require 'socialmux/result'
4
+ require 'socialmux/strategy'
5
+ require 'socialmux/auth_mapper'
6
+
7
+ module Socialmux
8
+ end
9
+
data/socialmux.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'socialmux/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "socialmux"
8
+ spec.version = Socialmux::VERSION
9
+ spec.authors = ["Stefano Verna"]
10
+ spec.email = ["stefano.verna@welaika.com"]
11
+ spec.description = %q{Socialmux implements a strategy to add multiple social providers to the same user. It's meant to be used togheter with OmniAuth.}
12
+ spec.summary = %q{Socialmux implements a strategy to add multiple social providers to the same user. It's meant to be used togheter with OmniAuth.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'coveralls'
24
+ spec.add_development_dependency 'mocha'
25
+ spec.add_development_dependency 'bourne'
26
+ spec.add_development_dependency 'rspec'
27
+
28
+ spec.add_dependency 'activesupport'
29
+ spec.add_dependency 'hashie'
30
+ end
31
+