omniauth-multiprovider 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -18
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/{LICENSE → LICENSE.txt} +0 -0
  8. data/README.md +52 -7
  9. data/Rakefile +2 -0
  10. data/lib/omniauth-multiprovider.rb +3 -1
  11. data/lib/omniauth/multiprovider.rb +0 -1
  12. data/lib/omniauth/multiprovider/config.rb +25 -0
  13. data/lib/omniauth/multiprovider/controllers/callbacks_controller.rb +43 -3
  14. data/lib/omniauth/multiprovider/error.rb +24 -1
  15. data/lib/omniauth/multiprovider/models/active_record.rb +14 -0
  16. data/lib/omniauth/multiprovider/models/authentication.rb +1 -9
  17. data/lib/omniauth/multiprovider/models/concerns/omni_authenticable.rb +79 -32
  18. data/lib/omniauth/multiprovider/version.rb +1 -1
  19. data/lib/omniauth/provider/abstract.rb +6 -38
  20. data/lib/omniauth/provider/generic.rb +10 -13
  21. data/omniauth-multiprovider.gemspec +15 -2
  22. data/spec/omniauth/error_spec.rb +30 -0
  23. data/spec/omniauth/multiprovider/controllers/callbacks_controller_spec.rb +90 -0
  24. data/spec/omniauth/multiprovider/models/active_record_spec.rb +21 -0
  25. data/spec/omniauth/multiprovider/models/concerns/omni_authenticable_spec.rb +67 -0
  26. data/spec/omniauth/provider/abstract_spec.rb +15 -0
  27. data/spec/omniauth/provider/facebook_spec.rb +13 -0
  28. data/spec/omniauth/provider/generic_spec.rb +13 -0
  29. data/spec/rails_app/.gitignore +17 -0
  30. data/spec/rails_app/Rakefile +6 -0
  31. data/spec/rails_app/app/assets/images/.keep +0 -0
  32. data/spec/rails_app/app/assets/javascripts/application.js +16 -0
  33. data/spec/rails_app/app/assets/stylesheets/application.css +15 -0
  34. data/spec/rails_app/app/controllers/application_controller.rb +5 -0
  35. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  36. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  37. data/spec/rails_app/app/mailers/.keep +0 -0
  38. data/spec/rails_app/app/models/.keep +0 -0
  39. data/spec/rails_app/app/models/concerns/.keep +0 -0
  40. data/spec/rails_app/app/models/user.rb +4 -0
  41. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  42. data/spec/rails_app/bin/bundle +3 -0
  43. data/spec/rails_app/bin/rails +4 -0
  44. data/spec/rails_app/bin/rake +4 -0
  45. data/spec/rails_app/bin/setup +29 -0
  46. data/spec/rails_app/config.ru +4 -0
  47. data/spec/rails_app/config/application.rb +26 -0
  48. data/spec/rails_app/config/boot.rb +3 -0
  49. data/spec/rails_app/config/database.yml +25 -0
  50. data/spec/rails_app/config/environment.rb +5 -0
  51. data/spec/rails_app/config/environments/test.rb +42 -0
  52. data/spec/rails_app/config/initializers/assets.rb +11 -0
  53. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
  55. data/spec/rails_app/config/initializers/devise.rb +259 -0
  56. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  57. data/spec/rails_app/config/initializers/inflections.rb +16 -0
  58. data/spec/rails_app/config/initializers/mime_types.rb +4 -0
  59. data/spec/rails_app/config/initializers/session_store.rb +3 -0
  60. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  61. data/spec/rails_app/config/locales/en.yml +23 -0
  62. data/spec/rails_app/config/routes.rb +3 -0
  63. data/spec/rails_app/config/secrets.yml +22 -0
  64. data/spec/rails_app/db/seeds.rb +7 -0
  65. data/spec/rails_app/lib/assets/.keep +0 -0
  66. data/spec/rails_app/lib/tasks/.keep +0 -0
  67. data/spec/rails_app/public/404.html +67 -0
  68. data/spec/rails_app/public/422.html +67 -0
  69. data/spec/rails_app/public/500.html +66 -0
  70. data/spec/rails_app/public/favicon.ico +0 -0
  71. data/spec/rails_app/public/robots.txt +5 -0
  72. data/spec/spec_helper.rb +43 -0
  73. data/spec/support/shared_examples_for_providers.rb +27 -0
  74. metadata +226 -7
  75. data/lib/omniauth/multiprovider/helpers.rb +0 -23
  76. data/lib/omniauth/provider/guests.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96e73d3f9fb27f6b97a817cd686171e911d5060e
4
- data.tar.gz: ed5755e6cb69e323a80f6052e62824f14951c669
3
+ metadata.gz: c1f3a5a4a52bb8284020af1e7399416e1b1b34d6
4
+ data.tar.gz: 15ec2a53af0a3a883291ad7c0c8dcb3772c19bef
5
5
  SHA512:
6
- metadata.gz: d7397943a451ddd556ef854f442aad422ccc11e8be4ee0b31483c7921a7e2a8d936eb2f8350b98a34436eb6c5fa8ac628dfccf877599fee5e64b10c6c993b7c5
7
- data.tar.gz: fe84283f3ae92745c71912739c8bf10d02a48ec726f45e460854e37ad072f63ef265c286e1f853002bb0c335074284935dbbebbf9fccb3cca64062a7d7bf779b
6
+ metadata.gz: cda70641cc3aea4b3998bb02e13b22cf9bb0e77dd7bf459d4bb9496841392489be746885a0a402f1a2d1d5da5b5fdf3a86b186b3cc0d792ddca611c90d65789a
7
+ data.tar.gz: 4ba92b422f566f0b1f7e2e3c88578c05a3e51b939ed2469c6616f28e01f10ee56e2ec1db0d02738c3f8e2c2d95a90dd95ef1fb1a9becf8789322db1ce65b40f5
data/.gitignore CHANGED
@@ -1,19 +1,14 @@
1
- *.rbc
2
- *.sassc
3
- .sass-cache
4
- capybara-*.html
5
- .rspec
6
- .rvmrc
7
- /.bundle
8
- /vendor/bundle
9
- /log/*
10
- /tmp/*
11
- /db/*.sqlite3
12
- /public/system/*
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
13
5
  /coverage/
14
- /spec/tmp/*
15
- **.orig
16
- rerun.txt
17
- pickle-email-*.html
18
- .project
19
- config/initializers/secret_token.rb
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1 @@
1
+ omniauth-multiprovider
@@ -0,0 +1 @@
1
+ ruby-2.1.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in omniauth-multiprovider.gemspec
4
+ gemspec
File without changes
data/README.md CHANGED
@@ -1,17 +1,33 @@
1
- omniauth-multiprovider
2
- ======================
1
+ # Omniauth::Multiprovider
2
+
3
3
 
4
4
  An easy way to authenticate users through many oauth providers (i.e. facebook, twitter, github and custom providers)
5
5
 
6
6
  No more OmniauthCallbacksController, no more complex method to relate users with tokens
7
7
 
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'omniauth-multiprovider'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install omniauth-multiprovider
23
+
8
24
  ## Disclaimer
9
25
 
10
26
  This is a work in progress. Expect serious refactors, breaking changes.
11
27
 
12
- I am almose sure that this gem will not work outside an Rails project. Sorry Sinatra lovers... I will try to remove any DHH opinion ;)
28
+ I am almost sure that this gem will not work outside an Rails project. Sorry Sinatra lovers... I will try to remove any DHH opinion ;)
13
29
 
14
- ## How to use
30
+ ## Usage
15
31
 
16
32
  In your devise resource model (aka mapping), usually `User` add:
17
33
 
@@ -32,26 +48,55 @@ Create a migration to add the `Authentication` model with:
32
48
  The change method should contain something like:
33
49
 
34
50
  create_table :authentications do |t|
35
- t.references :{devise_mapping_name}
51
+ t.references :{devise_mapping_name}, polymorphic: true
36
52
  t.string :uid, null: false
37
53
  t.string :provider, null: false
38
54
  t.string :access_token
39
55
  t.string :permissions
40
56
  t.timestamps
41
57
  end
42
-
58
+
43
59
  add_index :authentications, [:provider, :uid], unique: true
44
60
 
45
61
  **devise_mapping_name** is probably `user`
46
62
 
63
+ ### Errors and handling
64
+
65
+ There are 3 error scenarios:
66
+
67
+ * The oauth authentication already exists and belongs to the current logged-in user (it's trying to reconnect)
68
+ * The oauth authentication already exists and belongs to a different user (potential identity theft attemp)
69
+ * The oauth-provided email is already in use
70
+
71
+ The first error will be ignored by default.
72
+
73
+ For the other two errors `OmniAuth::MultiProvider::CallbacksController` will set the `flash[:alert]` to the localized I18N keys:
74
+
75
+ * common prefix: `devise.callbacks.user`
76
+ * `bound_to_other`
77
+ * `email_taken`
78
+
79
+
47
80
  ## Testing
48
81
 
49
- I am porting this gem from an existing project, the testing is currently embebed in the project. I will extract those test and cover this project.
82
+ Run `rspec`.
83
+
84
+ More tests are appreciated.
50
85
 
51
86
  ##Contributors
52
87
 
53
88
  [German DZ](https://twitter.com/GermanDZ)
89
+ [Abel Muiño](https://twitter.com/amuino)
54
90
 
55
91
  ## License
56
92
 
57
93
  MIT License.
94
+
95
+
96
+ ## Contributing
97
+
98
+ 1. Fork it ( https://github.com/[my-github-username]/omniauth-multiprovider/fork )
99
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
100
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
101
+ 4. Push to the branch (`git push origin my-new-feature`)
102
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -1,4 +1,5 @@
1
1
  require 'omniauth/multiprovider'
2
+ require 'omniauth/multiprovider/models/active_record'
2
3
 
3
4
  autoload :Authentication, 'omniauth/multiprovider/models/authentication'
4
5
  module OmniAuth
@@ -7,11 +8,12 @@ module OmniAuth
7
8
  autoload :OmniAuthenticable, 'omniauth/multiprovider/models/concerns/omni_authenticable'
8
9
  autoload :EmailMockups, 'omniauth/multiprovider/models/email_mockups'
9
10
  autoload :Error, 'omniauth/multiprovider/error'
11
+ autoload :AlreadyBoundError, 'omniauth/multiprovider/error'
10
12
  end
11
13
  module Provider
12
14
  autoload :Abstract, 'omniauth/provider/abstract'
13
15
  autoload :Generic, 'omniauth/provider/generic'
14
16
  autoload :Facebook, 'omniauth/provider/facebook'
15
- autoload :Guests, 'omniauth/provider/guests'
16
17
  end
17
18
  end
19
+
@@ -1,2 +1 @@
1
- require 'omniauth/multiprovider/helpers'
2
1
  require 'omniauth/multiprovider/version'
@@ -0,0 +1,25 @@
1
+ module OmniAuth
2
+ module MultiProvider
3
+ class Config
4
+ def authentication_klass
5
+ Authentication
6
+ end
7
+
8
+ def resource_klass
9
+ User
10
+ end
11
+
12
+ def resource_mapping
13
+ :user
14
+ end
15
+
16
+ def current_resource
17
+ "current_#{resource_mapping}".to_sym
18
+ end
19
+
20
+ def authentication_relationship_name
21
+ :authentications
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,9 +1,49 @@
1
1
  module OmniAuth
2
2
  module MultiProvider
3
3
  class CallbacksController < Devise::OmniauthCallbacksController
4
- def self.add_callback(provider, &block)
5
- self.send :define_method, provider, &block
4
+
5
+ protected
6
+
7
+ def self.create_handler(provider_name)
8
+ provider_class =
9
+ begin
10
+ "OmniAuth::Provider::#{provider_name.to_s.camelize}".constantize
11
+ rescue NameError
12
+ OmniAuth::Provider::Generic
13
+ end
14
+ define_method provider_name do
15
+ memo_name = "@_#{provider_name}"
16
+ provider = instance_variable_get memo_name
17
+ unless provider.present?
18
+ provider = provider_class.new(self)
19
+ instance_variable_set memo_name, provider
20
+ end
21
+ handle_request provider
22
+ end
23
+ end
24
+
25
+ def handle_request(provider)
26
+ provider.handle_request
27
+ rescue OmniAuth::MultiProvider::Error => error
28
+ raise unless handle_provider_error(error)
29
+ rescue RuntimeError => error
30
+ raise unless handle_unexpected_error(error)
31
+ end
32
+
33
+ def handle_provider_error(error)
34
+ set_flash_message :alert, error.message
35
+ redirect_to after_sign_in_path_for(resource_name)
36
+ true
37
+ end
38
+
39
+ def handle_unexpected_error(error)
40
+ false # no explicit handling
41
+ end
42
+
43
+ # TODO: this looks like a good candidate for using method_missing
44
+ Devise.omniauth_providers.each do |provider_name|
45
+ create_handler provider_name
6
46
  end
7
47
  end
8
48
  end
9
- end
49
+ end
@@ -2,5 +2,28 @@ module OmniAuth
2
2
  module MultiProvider
3
3
  class Error < RuntimeError
4
4
  end
5
+
6
+ class AlreadyBoundError < Error
7
+ def initialize(current, bound_to)
8
+ @current = current
9
+ @bound_to = bound_to
10
+ end
11
+
12
+ def message
13
+ if @current == @bound_to
14
+ 'bound_to_same'
15
+ else
16
+ 'bound_to_other'
17
+ end
18
+ end
19
+
20
+ attr_reader :current, :bound_to
21
+ end
22
+
23
+ class EmailTakenError < Error
24
+ def message
25
+ 'email_taken'
26
+ end
27
+ end
5
28
  end
6
- end
29
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_record'
2
+ require 'omniauth/multiprovider/config'
3
+
4
+ module OmniAuth
5
+ module MultiProvider
6
+ module ActiveRecord
7
+ def omniauthenticable
8
+ include OmniAuth::MultiProvider::OmniAuthenticable
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Base.extend OmniAuth::MultiProvider::ActiveRecord
@@ -1,7 +1,7 @@
1
1
  require 'hashugar'
2
2
 
3
3
  class Authentication < ActiveRecord::Base
4
- belongs_to OmniAuth::MultiProvider::resource_mapping
4
+ belongs_to :resource, polymorphic: true
5
5
  validates :provider, :uid, presence: true
6
6
 
7
7
  def self.from(omniauth_data, resource)
@@ -24,14 +24,6 @@ class Authentication < ActiveRecord::Base
24
24
  normalized
25
25
  end
26
26
 
27
- def resource
28
- send OmniAuth::MultiProvider::resource_mapping
29
- end
30
-
31
- def resource=(resource)
32
- send("#{OmniAuth::MultiProvider::resource_mapping}=", resource)
33
- end
34
-
35
27
  private
36
28
 
37
29
  def self.find_or_create(auth)
@@ -1,48 +1,95 @@
1
+
1
2
  module OmniAuth
2
3
  module MultiProvider
3
4
 
4
5
  module OmniAuthenticable
5
- extend ActiveSupport::Concern
6
-
7
- def self.authenticate_from_params(params)
8
- resource = nil
9
- if params[:username].present? && params[:password].present?
10
- resource = MultiProvider::resource_klass.find_for_database_authentication(email: params[:username])
11
- resource = nil if resource.nil? || !resource.valid_password?(params[:password])
12
- end
13
- if params[:provider].present?
14
- omniauth_data = {
15
- provider: params[:provider],
16
- uid: params[:user_id],
17
- credentials: {
18
- token: params[:access_token]
19
- }
20
- }
21
- provider_class = "OmniAuth::Provider::#{params[:provider].camelize}".constantize
22
- resource = provider_class.authenticate_from_oauth(params[:provider], omniauth_data)
23
- end
24
- resource
25
- end
6
+ extend ::ActiveSupport::Concern
26
7
 
27
8
  included do
28
- include MultiProvider::EmailMockups
9
+ include OmniAuth::MultiProvider::EmailMockups
10
+
11
+ class << self
12
+ def _oamp
13
+ @_omniauth_multiprovider_config ||= OmniAuth::MultiProvider::Config.new
14
+ end
15
+
16
+ def from_oauth(omniauth_data, signed_in_resource=nil)
17
+ auth = _oamp.authentication_klass.normalize(omniauth_data)
18
+ provider_name = auth.provider
19
+ access_token = auth.credentials.token
20
+ authentication = _oamp.authentication_klass.find_by(provider: provider_name, uid: auth.uid)
21
+
22
+ resource = authentication.try :resource
29
23
 
30
- has_many MultiProvider::authentication_relationship_name, dependent: :destroy, inverse_of: MultiProvider::resource_mapping, autosave: true, class_name: MultiProvider::authentication_klass.name do
31
- def [](provider)
32
- find_by(provider: provider)
24
+ if resource and signed_in_resource
25
+ signed_in_resource.oauth_already_bound resource, auth
26
+ return signed_in_resource
27
+ end
28
+ unless resource
29
+ attributes = oauth_to_attributes auth
30
+ if signed_in_resource
31
+ signed_in_resource.update_from_oauth attributes, auth
32
+ resource = signed_in_resource
33
+ else
34
+ resource = create_from_oauth attributes, auth
35
+ end
36
+ _oamp.authentication_klass.from(auth, resource)
37
+ end
38
+ resource
39
+ end
40
+
41
+ # Can be customized in each model
42
+ def oauth_to_attributes(oauth_data)
43
+ attrs = {
44
+ email: oauth_data.info[:email] || mock_email(oauth_data.provider, oauth_data.uid),
45
+ password: Devise.friendly_token[0,20]
46
+ }
47
+ attrs[:password_confirmation] = attrs[:password]
48
+ return attrs
49
+ end
50
+
51
+ # Can be customized in each model
52
+ def create_from_oauth(attributes, signed_in_resource = nil, oauth_data)
53
+ raise OmniAuth::MultiProvider::EmailTakenError if exists?(email: attributes[:email])
54
+ create!(attributes)
33
55
  end
34
56
  end
35
57
 
36
- omniauth_providers.each do |provider_name|
37
- begin
38
- klass = "OmniAuth::Provider::#{provider_name.to_s.camelize}".constantize
39
- rescue NameError
40
- klass = OmniAuth::Provider::Generic
58
+ has_many _oamp.authentication_relationship_name,
59
+ dependent: :destroy,
60
+ inverse_of: :resource,
61
+ as: :resource,
62
+ autosave: true,
63
+ class_name: _oamp.authentication_klass.name do
64
+ def [](provider)
65
+ find_by(provider: provider)
66
+ end
41
67
  end
42
- klass.init(provider_name)
68
+ end
69
+
70
+ # Handler for the case when there is already an Authentication
71
+ # Can be customized in each model
72
+ def oauth_already_bound(other, oauth_data)
73
+ if self == other
74
+ # update
75
+ self.update_from_oauth self.class.oauth_to_attributes(oauth_data), oauth_data
76
+ else
77
+ # raise error if the oauth data is bound to another resource
78
+ raise MultiProvider::AlreadyBoundError.new self, other
43
79
  end
80
+ end
44
81
 
82
+ # Handler for the case when a signed_in_resource exists and is being authenticated
83
+ # Can be customized in each model
84
+ def update_from_oauth(new_attrs, oauth)
85
+ unless self.email.blank? || new_attrs[:email] == self.email
86
+ # refusing to change existing email
87
+ new_attrs.delete(:email)
88
+ # but check if someone else is using it anyway
89
+ raise OmniAuth::MultiProvider::EmailTakenError if User.exists?(email: new_attrs[:email])
90
+ end
91
+ self.update!(new_attrs)
45
92
  end
46
93
  end
47
94
  end
48
- end
95
+ end