socialmux 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 (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
+