From 600adde6bad8e050215402e7cca7465f459b81e0 Mon Sep 17 00:00:00 2001 From: Stella Rouzi Date: Wed, 18 Jun 2014 16:58:30 +0300 Subject: [PATCH] implement openID authentication --- Gemfile | 5 + Gemfile.lock | 36 ++++++ app/assets/images/facebook.png | Bin 0 -> 1431 bytes app/assets/images/google.png | Bin 0 -> 1615 bytes app/assets/images/novell.png | Bin 0 -> 413 bytes app/controllers/registrations_controller.rb | 4 + .../users/omniauth_callbacks_controller.rb | 32 ++++++ app/models/openid.rb | 22 ++++ app/models/user.rb | 28 ++++- app/views/devise/registrations/edit.html.haml | 12 ++ app/views/devise/registrations/new.html.haml | 16 ++- app/views/devise/sessions/new.html.haml | 21 ++++ app/views/devise/shared/_links.erb | 19 +++ app/views/devise/shared/_openid.html.haml | 4 + app/views/layouts/_navigation.html.haml | 1 + config/initializers/devise.rb | 12 ++ config/routes.rb | 4 +- db/migrate/20140603092041_create_openids.rb | 12 ++ db/schema.rb | 15 ++- spec/features/omniauth_spec.rb | 108 ++++++++++++++++++ spec/spec_helper.rb | 5 + spec/support/login_macros.rb | 2 +- spec/support/omniauth_macros.rb | 64 +++++++++++ 23 files changed, 408 insertions(+), 14 deletions(-) create mode 100644 app/assets/images/facebook.png create mode 100644 app/assets/images/google.png create mode 100644 app/assets/images/novell.png create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb create mode 100644 app/models/openid.rb create mode 100644 app/views/devise/sessions/new.html.haml create mode 100644 app/views/devise/shared/_links.erb create mode 100644 app/views/devise/shared/_openid.html.haml create mode 100644 db/migrate/20140603092041_create_openids.rb create mode 100644 spec/features/omniauth_spec.rb create mode 100644 spec/support/omniauth_macros.rb diff --git a/Gemfile b/Gemfile index 274ad086..947b17c8 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,11 @@ gem 'paper_trail' # Use devise as authentification framework gem 'devise' +# Use omniauth to support openID authentication +gem 'omniauth' +gem 'omniauth-facebook' +gem 'omniauth-openid' +gem 'omniauth-google-oauth2' # Use cancan as authorization framework gem 'cancan' diff --git a/Gemfile.lock b/Gemfile.lock index ea81b1c9..5b210e94 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,6 +103,8 @@ GEM factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) ffi (1.9.3) formatador (0.2.4) formtastic (2.3.0.rc3) @@ -126,6 +128,7 @@ GEM activesupport (>= 4.0.1) haml (>= 3.1, < 5.0) railties (>= 4.0.1) + hashie (2.1.1) hike (1.2.3) hoptoad_notifier (2.4.11) activesupport @@ -143,6 +146,7 @@ GEM jquery-ui-rails (4.2.1) railties (>= 3.2.16) json (1.8.1) + jwt (1.0.0) launchy (2.4.2) addressable (~> 2.3) letter_opener (1.2.0) @@ -162,10 +166,34 @@ GEM mini_portile (0.6.0) minitest (5.3.3) multi_json (1.9.2) + multi_xml (0.5.5) + multipart-post (2.0.0) mysql2 (0.3.16) nio4r (1.0.0) nokogiri (1.6.2.1) mini_portile (= 0.6.0) + oauth2 (0.9.4) + faraday (>= 0.8, < 0.10) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (~> 1.2) + omniauth (1.2.1) + hashie (>= 1.2, < 3) + rack (~> 1.0) + omniauth-facebook (1.6.0) + omniauth-oauth2 (~> 1.1) + omniauth-google-oauth2 (0.2.4) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) + omniauth-oauth2 (1.1.2) + faraday (>= 0.8, < 0.10) + multi_json (~> 1.3) + oauth2 (~> 0.9.3) + omniauth (~> 1.2) + omniauth-openid (1.0.1) + omniauth (~> 1.0) + rack-openid (~> 1.3.1) orm_adapter (0.5.0) paper_trail (3.0.1) activerecord (>= 3.0, < 5.0) @@ -196,6 +224,9 @@ GEM quiet_assets (1.0.2) railties (>= 3.1, < 5.0) rack (1.5.2) + rack-openid (1.3.1) + rack (>= 1.1.0) + ruby-openid (>= 2.1.8) rack-test (0.6.2) rack (>= 1.0) rails (4.1.0) @@ -259,6 +290,7 @@ GEM powerpack (~> 0.0.6) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) + ruby-openid (2.5.0) ruby-progressbar (1.5.1) rubyzip (1.0.0) sass (3.2.19) @@ -346,6 +378,10 @@ DEPENDENCIES jquery-ui-rails letter_opener mysql2 + omniauth + omniauth-facebook + omniauth-google-oauth2 + omniauth-openid paper_trail paperclip prawn_rails diff --git a/app/assets/images/facebook.png b/app/assets/images/facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..e8bca65fd18e68bc9986f89138e9d26f46c928ac GIT binary patch literal 1431 zcmV;I1!($-P)O)mmvrv;GCO9>_SpXy|k1<1Cm1M_hbhP zl0&lD=KKzqfI1=k5;;=QAw$=k~oI-NGCj)bTbKS!G#6ahLB#(4lifHMpr#L3;+S`QA0+d07t=rar@%YUe=|x1q&RdWG>wk}R+J*;Y~~>w!U3rUoV{{tn;jRsan4L< z6Jm1_|MTZBx3Vl*leUW$$Yg3N_0;z$KzL8@T(g;(Znv+XX%UmIwK8@glJ(u~pBB+q z1$~x~V=S~yAE&a~Jlbjewf@~ZQ8wQ zK~#9!mVSaIA|e!R!IoP5D#joQpiPJxY7C7D!NjNpF-l@8 zi1DY2NsWn#7!t$;1BekdAhv0w)F>aJ(w2_~X`x38y|#DI_If+x?9Sfa?)9z^ewjGw z+jn;Moqe8pKW65D|9c~M`R;BSzax;AmX_Q_ix$<3?zFm(enng70IKHaVfVYs9Cke` zAA{C+1)Oss;zLA2a306vJdRB`f@dSquh(7;T~A41@7}%nwY9Z}RaJfPt*`#VzT>?h zlo%ijW@TZ|rbm(Ka~Zag&UOUWt^j8eRkx7@WZxKvd*yW9_WhmkrF)R(b;Fb9#_+fXkBg#m&U863HxK+q0PNCvC@vLLQG`%X zl}ar{Cr?OBjMe5(qhxsJ@YpdJv;?S)fEtNJX6T)xfA_;HfV~E|OK6wT|L)H|gvO%Q z1mCLXLDRjUUXLY;#|6c$3Q`IbRj~*WDhXZAyl1f;*G>S8F(V{Y7%F0tq7b;mM^s(J z>PMp-EnPP(dv{qSh*&*wxujNOqJZe2x)@70y`X4}pt0W#9!c^hf{&3JXJFde_=d(o4wqQ@uJq-iMa52H9ZiscuE zQ4}1v(QsCNN&;)EXW^$a{kS#~G3Uy9e_~#a@qRFP4X*`TaOmYyjOdlgG-1M|)}m<5 zcLPxk+p2O=bvcAL+xoHOa(EJsBLqLR{$ut1UKO9OE5m(x>DFfRVpTC#mKR`X;ufAd z*@$loRgAbP;&F=YMDbAQe{F?6)U7JUtIG?}m#(DtK*P>I(9|8p$Lp71W{wYq*=g9d zVKMrK#*Ccs3(>)6|ME75BVoB>W7W(Ak3I9a#7K$1BPHoLvbqR3dp$V;JzHBl2C!^v z6Us#;l!(fJ}G<232uotwFht;59nK^Zpbty!A*Ve zM4w~Q#>NoBUKp|=XkMUbty_qvQafRC*|Q@JF^l8T${1% z@OvhZCbeewj6L?r88|%f<>nf+?RXBaJUs_X3eJG_ZUnnf19plS!UJ0!lWX!9;{Am$ z#KB|LVD|#&PA?>EL=FNK_s;;24uPF62k#OWkp3M&s5xHdSGR#(tTk+`a4dK>zH&n} z8@SyQ$uC9@0v>w9&)Nk*__RE&xp`{GM1Kx`4}Px3nv?C$K#6ZgLj3Lh)b5`|AT#vE z0~lEODrljcep(blubI%E90w(4F z=Sfb?WM!DQMx2)=GTzJvm`Fkh@s%5MntD&#Vy`*FkhmAzZXYlP9y%Z{d|C1$shJW@ z=o9h2XroNR=Fd!EbHy4@vI*G$GCV~o35<@@E5u(8T5$>_f1T3b&R&Z~N{9=iDztaY8;@6P`C#`qK7Vn6RW!jS8~bu` z_shnm;j5@Wv|YyDsw^JwC%QZKJf>YFd%Ejsceq`Q+!ANbguE_N;-Zv#gkjbr)z$MG z8XDG=m6cTs17er%Yhm)uiB__!{YN4{N^G@F%wHh!y5WRa{aZsPkJF=peYAJ9Q`ih~ z#>N@v5t}QFrV1dx81#z)HzmX+#uRnGZX zkdUy9GS1i?{Wdgc8UI10f^p3y33JVRtGu{z`8!FHaU2Byz4osF0|3bfn|->$iJkxe N002ovPDHLkV1hOX`1Sw* literal 0 HcmV?d00001 diff --git a/app/assets/images/novell.png b/app/assets/images/novell.png new file mode 100644 index 0000000000000000000000000000000000000000..a33e55e2e23d64f0bcee51a445f2ffa1a1a8efdb GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyO9OmDT>ra<{RaXD@&7?d{~4J7 zGw}RZwfKMN`2U9P|Cwd~KYse(J@x;>mH*3||G#|opF!Y1i`4%|PyQRa{J(hhzo6Rx z-3R~w{PkbY;s2(c|7R@vpOE)|)yDslI{%C6{$IcS-^%AdgV_Ju_x>}8{GT-Yzq#lC zKY#vk#1_kS*h{~W^q zSw#Pft5hEQXbE&MV@Z%-FoVOh8)+a;lDE4HLkFv@2av;A;1OBOz`!jG!i)^F=12eq z*-JcqUD@xkvGAC4zv4B$02C_pba4!^IKB1KMy>-20?Yx$v3!MDvR?le{r_Lgx-mg? z^3k*@?RQ+ABLtKqIWIlGbN4+TgD%4Z<_@kcirw}yMUH6c)I$ztaD0e F0sy;#xo7|Y literal 0 HcmV?d00001 diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index cd3a9ca9..b6137bac 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,6 +1,10 @@ class RegistrationsController < Devise::RegistrationsController before_action :configure_permitted_parameters, if: :devise_controller? + def edit + @openids = Openid.where(user_id: current_user.id).order(:provider) + end + def update @user = User.find(current_user.id) email_changed = false diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 00000000..6b9004b4 --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,32 @@ +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + skip_before_filter :verify_authenticity_token + + [:novell, :google, :facebook, :twitter].each do |provider| + define_method(provider) { handle(provider) } + end + + private + def handle(provider) + + auth_hash = request.env['omniauth.auth'] + openid = Openid.find_for_oauth(auth_hash) # Get or create openid + # If openid exists and is associated with a user, sign in with associated user, + # even if the email of the associated user and the email of the provided openid are different + unless user = openid.user + user = User.find_for_auth(auth_hash, current_user) # Get or create users + end + + begin + user.save! + if openid.user != user + openid.user = user + end + openid.save! + + sign_in user + redirect_to root_path, notice: user.email + " signed in successfully with #{provider}" + rescue Exception => e + redirect_back_or_to new_user_registration_path, alert: 'Failed' + e.message + end + end +end \ No newline at end of file diff --git a/app/models/openid.rb b/app/models/openid.rb new file mode 100644 index 00000000..b85a1525 --- /dev/null +++ b/app/models/openid.rb @@ -0,0 +1,22 @@ +class Openid < ActiveRecord::Base + belongs_to :user + validates :provider, :uid, :presence => true + + # Searches for openid based on provider and uid. + # Returns found openid or a new openid. + # ====Returns + # * Openid::ActiveRecord_Relation -> openid + def self.find_for_oauth(auth) + openid = Openid.where(provider: auth.provider, uid: auth.uid).first_or_initialize + + if openid.new_record? + openid.email = auth.info.email + if existing_openid = Openid.where(email: openid.email).first + openid.user_id = existing_openid.user_id + end + end + openid + + end + +end diff --git a/app/models/user.rb b/app/models/user.rb index 21200f4a..3bd4fad8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,13 +4,15 @@ class User < ActiveRecord::Base # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable, - :confirmable + :recoverable, :rememberable, :trackable, :validatable, :confirmable, + :omniauthable, omniauth_providers: [:novell, :google, :facebook] has_and_belongs_to_many :roles has_one :person, :inverse_of => :user + has_many :openids - attr_accessible :email, :password, :password_confirmation, :remember_me, :role_id, :role_ids, :person_attributes + attr_accessible :email, :password, :password_confirmation, :remember_me, :role_id, :role_ids, + :person_attributes accepts_nested_attributes_for :person accepts_nested_attributes_for :roles @@ -19,6 +21,26 @@ class User < ActiveRecord::Base delegate :last_name, :first_name, :public_name, to: :person + # Searches for user based on email. Returns found user or new user. + # ====Returns + # * +User::ActiveRecord_Relation+ -> user + def self.find_for_auth(auth, current_user = nil) + + user = current_user + + if user.nil? # No current user available, user is not already logged in + user = User.where(email: auth.info.email).first_or_initialize + end + + if user.new_record? + user.email = auth.info.email + user.password = Devise.friendly_token[0,20] + user.skip_confirmation! + end + + user + end + def role?(role) Rails.logger.debug("Checking role in user") !!roles.find_by_name(role.to_s.downcase.camelize) diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index 435b5bff..36992da5 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -15,6 +15,18 @@ %br %br = render :partial => 'devise/registrations/volunteerperson', :locals => {:p => p} + = f.inputs :name => 'OpenID' do + %h4 + Currently using the following openIDs: + - @openids.each do |openid| + = openid.provider + = openid.email + %br + %h4 + To add an openID to your account using a different email address, sign in with your + openID while logged in to OSEM + = render 'devise/shared/openid' + = f.inputs :name => "Account" do = f.input :email, :required => false = f.input :password, :hint => "(Leave blank if you don't want to change it)", :input_html => {:autocomplete => "off"} diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d9e25ccd..1badafdb 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -3,13 +3,17 @@ %h1.text-center Sign Up .col-md-5.col-md-offset-1 .well - = semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| + = semantic_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| = f.input :email = f.input :password = f.input :password_confirmation - = f.action :submit, :as => :button, :label => "Sign Up", :button_html => {:class => "btn btn-primary"} - .col-md-3.col-md-offset-2 + = f.action :submit, as: :button, label: 'Sign Up', button_html: {class: 'btn btn-primary'} + %br + %br + %h4 Already have an account? + = render 'devise/shared/links' + .col-md-4 .well - %h4 - Already have an account? - = render "devise/shared/links" + %h4 Or use your openID + + = render 'devise/shared/openid' \ No newline at end of file diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml new file mode 100644 index 00000000..180aa561 --- /dev/null +++ b/app/views/devise/sessions/new.html.haml @@ -0,0 +1,21 @@ +.row + .page-header + %h2.text-center Sign In + .col-md-5.col-md-offset-1 + .well + = semantic_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| + = f.input :email + = f.input :password + - if devise_mapping.rememberable? + = f.check_box :remember_me + = f.label 'Remember me' + %br + = f.action :submit, as: :button, label: 'Sign In', button_html: {class: 'btn btn-primary'} + %br + %br + = render "devise/shared/links" + + .col-md-4 + .well + %h4 Or use your openID + = render 'devise/shared/openid' \ No newline at end of file diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb new file mode 100644 index 00000000..b9123456 --- /dev/null +++ b/app/views/devise/shared/_links.erb @@ -0,0 +1,19 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Sign in", new_user_session_path %>
+<% end -%> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end -%> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end -%> diff --git a/app/views/devise/shared/_openid.html.haml b/app/views/devise/shared/_openid.html.haml new file mode 100644 index 00000000..f9e2057e --- /dev/null +++ b/app/views/devise/shared/_openid.html.haml @@ -0,0 +1,4 @@ +- if devise_mapping.omniauthable? + - resource_class.omniauth_providers.each do |provider| + = link_to image_tag("#{provider}.png", size: '32x32') + provider , omniauth_authorize_path(resource_name, provider) + %br \ No newline at end of file diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index d63dd137..775c4bcf 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -51,5 +51,6 @@ %button.btn.btn-block Sign in %br = link_to "Forgot your password?", new_password_path('user') + = link_to 'Sign in with openID', new_user_session_path %li.hidden-lg = link_to('Sign In', new_user_session_path) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 2dc7d680..06d3d126 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,9 +1,21 @@ # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. + Devise.setup do |config| # ==> Secret key, generate one with `rake secret` config.secret_key = Rails.application.secrets.devise_secret_key + # ==> openIDs configuration + # Define the available openID providers that can be used to log in + # Pass each provider to User model in :omniauth_providers (for open_id providers use their name) + # Include provider in users/omniauth_callbacks_controller.rb + + config.omniauth :open_id, name: 'novell', identifier: 'http://www.opensuse.org/openid/user' + config.omniauth :google_oauth2, Rails.application.secrets.google_key, Rails.application.secrets.google_secret, { + name: 'google', + scope: 'email' } + config.omniauth :facebook, Rails.application.secrets.facebook_key, Rails.application.secrets.facebook_secret + # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class with default "from" parameter. diff --git a/config/routes.rb b/config/routes.rb index ae46877b..30c401ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,9 @@ Osem::Application.routes.draw do get 'conference/show' - devise_for :users, controllers: { registrations: :registrations }, path: 'accounts' + devise_for :users, :controllers => { registrations: :registrations, + omniauth_callbacks: 'users/omniauth_callbacks' }, + :path => 'accounts' namespace :admin do resources :users diff --git a/db/migrate/20140603092041_create_openids.rb b/db/migrate/20140603092041_create_openids.rb new file mode 100644 index 00000000..8c55e8fd --- /dev/null +++ b/db/migrate/20140603092041_create_openids.rb @@ -0,0 +1,12 @@ +class CreateOpenids< ActiveRecord::Migration + def change + create_table :openids do |t| + t.string :provider + t.string :email + t.string :uid + t.integer :user_id + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 030cce43..557c2177 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140617145048) do +ActiveRecord::Schema.define(version: 20140618062623) do create_table "answers", force: true do |t| t.string "title" @@ -82,11 +82,11 @@ ActiveRecord::Schema.define(version: 20140617145048) do t.text "description" t.text "registration_description" t.text "ticket_description" + t.text "sponsor_description" + t.string "sponsor_email" t.string "twitter_url" t.string "facebook_url" t.string "google_url" - t.text "sponsor_description" - t.string "sponsor_email" t.text "lodging_description" t.boolean "include_registrations_in_splash", default: false t.boolean "include_sponsors_in_splash", default: false @@ -213,6 +213,15 @@ ActiveRecord::Schema.define(version: 20140617145048) do t.string "website_link" end + create_table "openids", force: true do |t| + t.string "provider" + t.string "email" + t.string "uid" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "people", force: true do |t| t.string "guid", null: false t.string "first_name", default: "" diff --git a/spec/features/omniauth_spec.rb b/spec/features/omniauth_spec.rb new file mode 100644 index 00000000..9e9ef482 --- /dev/null +++ b/spec/features/omniauth_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +feature Openid do + let!(:participant_role) { create(:participant_role) } + let!(:admin_role) { create(:admin_role) } + + describe 'sign in with openid' do + + it 'has option to log in with Google account' do + visit '/accounts/sign_in' + expect(page.has_content?('Or use your openID')).to be true + expect(page.has_content?('google')).to be true + end + + it 'signs in *new* user with Google account' do + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + 1 + visit '/accounts/sign_in' + + mock_auth_new_user + click_link 'google' + expect(flash).to eq('test-1@gmail.com signed in successfully with google') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + end + + it 'signs in an existing user' do + create(:participant, email: 'test-participant-1@google.com') + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + visit '/accounts/sign_in' + + mock_auth_existing_user_participant + click_link 'google' + expect(flash).to eq('test-participant-1@google.com signed in successfully with google') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + end + + it 'can handle authentication error' do + OmniAuth.config.mock_auth[:google] = :invalid_credentials + visit '/accounts/sign_in' + expect(page.has_content?('Or use your openID')).to be true + click_link 'google' + expect(flash).to eq("Could not authenticate you from Google because \"Invalid credentials\".") + end + + it 'adds openid to existing user' do + # Sign in user + user = create(:participant, email: 'test-participant-1@google.com') + sign_in user + + # Add openID to current user + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + visit '/accounts/edit' + + mock_auth_new_user + click_link 'google' + expect(flash).to eq('test-participant-1@google.com signed in successfully with google') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + expect(Openid.where(email: 'test-1@gmail.com').first.nil?).to eq(false) + end + + it 'signs in with openID using the same email as another associated openid' do |user| + # Sign in user + user = create(:participant, email: 'test-participant-1@google.com') + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + visit '/accounts/sign_in' + + mock_auth_existing_user_participant + click_link 'google' + expect(flash).to eq('test-participant-1@google.com signed in successfully with google') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + + # Add openID to current user with email test-1@gmail.com + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + visit '/accounts/edit' + + mock_auth_new_user + click_link 'google' + expect(flash).to eq('test-participant-1@google.com signed in successfully with google') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + expect(Openid.where(email: 'test-participant-1@google.com').first.nil?).to eq(false) + expect(Openid.where(email: 'test-1@gmail.com').first.nil?).to eq(false) + + # Sign in with different openID using same email (test-1@gmail.com) + sign_out + expected_count_openid = Openid.count + 1 + expected_count_user = User.count + + visit '/accounts/sign_in' + mock_auth_new_user_fb + click_link 'facebook' + expect(flash).to eq('test-participant-1@google.com signed in successfully with facebook') + expect(Openid.count).to eq(expected_count_openid) + expect(User.count).to eq(expected_count_user) + last_openid = Openid.last + expect(last_openid.uid).to eq('facebook-test-uid-1') + expect(last_openid.email).to eq('test-1@gmail.com') + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 318b1650..0776c1b8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -53,6 +53,9 @@ RSpec.configure do |config| # Includes support/login_macros for feature tests config.include LoginMacros, type: :feature + # Includes omniauth macro + config.include(OmniauthMacros) + # Includes support/flash for feature tests config.include Flash, type: :feature @@ -63,3 +66,5 @@ RSpec.configure do |config| c.syntax = :expect end end + +OmniAuth.config.test_mode = true diff --git a/spec/support/login_macros.rb b/spec/support/login_macros.rb index f4608fc5..1bff7302 100644 --- a/spec/support/login_macros.rb +++ b/spec/support/login_macros.rb @@ -4,7 +4,7 @@ module LoginMacros fill_in 'user_email', with: user.email fill_in 'user_password', with: user.password - find(:xpath, "//div[@id='content']//input[@name='commit']").click + find(:xpath, "//div[@id='content']//button[@type='submit']").click expect(page.has_content?('Signed in successfully')).to be true end diff --git a/spec/support/omniauth_macros.rb b/spec/support/omniauth_macros.rb new file mode 100644 index 00000000..767e048f --- /dev/null +++ b/spec/support/omniauth_macros.rb @@ -0,0 +1,64 @@ +module OmniauthMacros + # The mock_auth configuration allows you to set per-provider (or default) + # authentication hashes to return during integration testing. + + def mock_auth_new_user + OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new({ + provider: 'google', + uid: 'google-test-uid-1', + info: { + email: 'test-1@gmail.com' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + } + }) + end + + def mock_auth_new_user_fb + OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({ + provider: 'google', + uid: 'facebook-test-uid-1', + info: { + email: 'test-1@gmail.com' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + } + }) + end + + def mock_auth_existing_user_participant + # The mock_auth configuration allows you to set per-provider (or default) + # authentication hashes to return during integration testing. + OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new({ + provider: 'google', + uid: 'google-test-uid-participant-1', + info: { + email: 'test-participant-1@google.com' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + } + }) + end + + def mock_auth_existing_user_admin + # The mock_auth configuration allows you to set per-provider (or default) + # authentication hashes to return during integration testing. + OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new({ + provider: 'google', + uid: 'google-test-uid-admin-1', + info: { + email: 'test-admin-1@google.com' + }, + credentials: { + token: 'mock_token', + secret: 'mock_secret' + } + }) + end +end \ No newline at end of file