Devise Controller Test - ActionController::UrlGenerationError - ruby-on-rails

I'm using OmniAuth through Devise in my Rails app. I'm trying to test that my callback method is being called properly and functions correctly. I am currently receiving an error when running my spec.
The error:
Failure/Error: get user_omniauth_authorize_path(:facebook)
ActionController::UrlGenerationError:
No route matches {:action=>"/users/auth/facebook", :controller=>"users/omniauth_callbacks"} missing required keys: [:action]
My spec:
#spec/controllers/users/omniauth_callbacks_controller_spec.rb
require 'rails_helper'
RSpec.describe Users::OmniauthCallbacksController, :type => :controller do
context 'get facebook' do
before do
request.env["devise.mapping"] = Devise.mappings[:user] # If using Devise
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
end
it 'should create user, redirect to homepage, and create session' do
get user_omniauth_authorize_path(:facebook)
expect(response).to redirect_to(user_omniauth_callback_path)
end
end
end
Support file:
#spec/support/omniauth.rb
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
:provider => 'facebook',
:uid => '123545',
:email => 'fake#fake.com'
})
Controller:
#app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.from_omniauth(request.env['omniauth.auth'])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => 'Facebook') if is_navigational_format? #todo what is this doing
else
session['devise.facebook_data'] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
end
Routes:
devise_for :users, :controllers => { :omniauth_callbacks => 'users/omniauth_callbacks' }
I think the issue is in how it's getting routed. I think action should just be 'facebook' not '/users/auth/facebook', but I don't know the right way to resolve this.

In case anyone stumbles on this looking for answers like I did.
I had this problem when adding a second Omniauth strategy. Turns out the problem was I'd forgotton to include the strategy in my model declaration
eg I was already authorising with google
# app/models/user.rb
devise :rememberable, :trackable, :omniauthable, :omniauth_providers => [:google]
but then wanted to add a second provider (eg facebook). I forgot to add facebook to the list of omniauth providers and so was getting that error when my spec ran. I fixed it by changing to
# app/models/user.rb
devise :rememberable, :trackable, :omniauthable, :omniauth_providers => [:google,:facebook]

I got the same error missing required keys: [:action] as you. After I read RSpec document, I found that the argument of get should be something like :index (the action name). Because I defined:
# app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# ...
end
end
So I changed get user_omniauth_authorize_path(:facebook) to get :facebook and added some omniauth mocks. Now it pass!

Related

ActionController::RoutingError (uninitialized constant Users::OmniauthCallbacksController) Devise and google_oauth2

I have followed the tutorial from this link and keep ending up with the following log messages:
Started GET "/users/auth/google_oauth2" for 127.0.0.1 at 2019-02-22 20:59:25 +1100
I, [2019-02-22T20:59:25.512091 #11001] INFO -- omniauth: (google_oauth2) Request phase initiated.
Started GET "/users/auth/google_oauth2/callback?state=...
I, [2019-02-22T20:59:29.060352 #11001] INFO -- omniauth: (google_oauth2) Callback phase initiated.
ActionController::RoutingError (uninitialized constant Users::OmniauthCallbacksController):
I've searched online and all the solutions suggest checking the spelling in various files. I've included them below.
Devise _links.html.erb:
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", user_google_oauth2_omniauth_authorize_path %><br />
<% end -%>
<% end -%>
devise.rb:
config.omniauth :google_oauth2, client_id, client_secret, {
scope: "contacts.readonly,userinfo.email,userinfo.profile,youtube",
prompt: 'select_account',
image_aspect_ratio: 'square',
image_size: 50
}
User.rb:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:google_oauth2]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.token = auth.credentials.token
user.expires = auth.credentials.expires
user.expires_at = auth.credentials.expires_at
user.refresh_token = auth.credentials.refresh_token
end
end
end
/app/controllers/users/omniauth_callbacks_controllers.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Google") if is_navigational_format?
else
session["devise.google_data"] = request.env["omniauth.auth"]
end
redirect_to '/'
end
end
routes.rb
devise_for :users, controllers: {omniauth_callbacks: "users/omniauth_callbacks"}
google console
I have set up my google console with the following Authorised redirect URLs:
http://localhost:3000/users/auth/google_oauth2/callback
Rails Routes
When I do a rails routes I have:
user_google_oauth2_omniauth_authorize GET|POST /users/auth/google_oauth2(.:format) users/omniauth_callbacks#passthru
user_google_oauth2_omniauth_callback GET|POST /users/auth/google_oauth2/callback(.:format) users/omniauth_callbacks#google_oauth2
Any assistance to know why this isn't working would be greatly appreciated.
/app/controllers/users/omniauth_callbacks_controllers.rb
This is not correct. You have an additional s in your controller name. This is why rails does not manage to find the class. You should rename your controller name to omniauth_callbacks_controller.rb.

Rails 5, Devise, Omniauth, Twitter

I know this has been asked many times, but the answers are never fully acceptable to me.
So I am following Ryan Bates' Railscast about this topic, and mixing that with the official Devise Omniauth guide (that is based on FB), but I am just not getting it to work like I expect, so I would love some help.
I have a Users::OmniauthCallbacksController that looks like this:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
#user = User.from_omniauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect root_path, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
else
session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
flash[:notice] = flash[:notice].to_a.concat resource.errors.full_messages
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
def failure
redirect_to root_path
end
end
Then I also have two methods on my User.rb
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.update(
email: auth.info.email,
password: Devise.friendly_token[0,20],
username: auth.info.nickname,
remote_avatar_url: auth.info.image,
token: auth.credentials.token,
secret: auth.credentials.secret
)
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.twitter_data"]
# user.attributes = params
user.update(
email: params[:email],
password: Devise.friendly_token[0,20],
username: data["info"]["nickname"],
remote_avatar_url: data["info"]["image"],
token: data["credentials"]["token"],
secret: data["credentials"]["secret"]
)
end
end
end
I run into a variety of problems. The most immediate is because I am setting the password, the user doesn't know the password when they try to login (and I don't auto sign them in upon confirmation).
But if I don't set the password, it doesn't ask them to set the password...so that's kinda weird too.
These are my devise settings on my User model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :omniauthable, :omniauth_providers => [:twitter]
validates :username,
presence: true,
uniqueness: {
case_sensitive: false
}
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
So my question is this, when someone signs up via Twitter, do they need to enter a password? I automatically send them to the registration/new.html.erb anyway because Twitter doesn't return an email value. But I am trying to just get the process working first, before optimizing it.
How do I deal with the password issue?
Edit 1
For more clarity, I will have to deal with this password_required issue regardless of the OAuth provider.
So how do I override that requirement for all OAuth providers?
You should add the following method to the User class:
def password_required?
(provider.blank? || uid.blank?) && super
end
Since Twitter doesn't return the user's email, you may also want to tweak that email validation, but redirecting the user to registration/new.html.erb like you are already doing seems like the correct approach to me.

Venmo / Oauth Missing argument: client_id., code: 241

I'm using oauth to try and login through venmo based on this guide. This isn't official and venmo docs don't even show code example with ruby.
At first, I couldn't even see the venmo login. I removed the ENV and brackets around the id and secret in the initializer and then I could see the login. When I submit and it hits the call back I get this error: (and I think it retries endlessly...I have to manually shut down server)
{"error": {"message": "Missing argument: client_id.", "code": 241}}
I, [2014-04-16T11:49:48.124423 #47700] INFO -- omniauth: (venmo) Callback phase initiated.
E, [2014-04-16T11:49:48.345202 #47700] ERROR -- omniauth: (venmo) Authentication failure! invalid_credentials: OAuth2::Error, {"message"=>"Missing argument: client_id.", "code"=>241}:
Here is my route:
get 'users/auth/venmo/callback' => 'omniauth_callbacks_controller#create'
Gems:
gem 'devise'
gem 'omniauth'
gem 'omniauth-venmo'
I have this in my initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :venmo, 'id', 'secret', :scope => 'access_feed,access_profile,access_friends,make_payments'
end
This is my callback controller
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def venmo
#user = User.find_for_oauth(env["omniauth.auth"], current_user)
raise
if #user.persisted?
sign_in_and_redirect root_path, :event => :authentication
set_flash_message(:notice, :success, :kind => "Venmo") if is_navigational_format?
else
session["devise.venmo_uid"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
protected
def auth_hash
request.env['omniauth.auth']
end
end
and I have initializers/devise.rb
require 'omniauth-venmo'
config.omniauth :venmo, "KEY", "SECRET"
-- with the values filled in of course. I would appreciate the help...
Thank you!!
UPDATE: I've pushed this to a github repository and one of my peers pulled it. He did not get the same error. What would the reason for this be..?
#Peege151... I created a working solution with devise integration as said by you..here is my code below, use it for reference purpose. I don't say this is the exact solution for the raised question but this may help since i got it working
Gems:
gem 'rails 3.2.14' # Important~
gem 'devise'
gem 'omniauth'
gem 'omniauth-venmo'
I have not created omniauth.rb in initializers .... instead i added configuration in devise.rb as u wanted it with devise
my devise.rb :
require "omniauth-venmo"
config.omniauth :venmo, '--Your APP KEY -- ', '-- App Secret--',{:client_options => {:ssl => {:verify => false}}}
above line i have set SSL verification to false,, if u want enable SSL with (:verify => true).
This is my callback controller code:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def venmo
#user = User.find_for_oauth(request.env["omniauth.auth"], current_user)
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Venmo") if is_navigational_format?
else
session["devise.twitter_uid"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
User.rb code:
class User < ActiveRecord::Base
TEMP_EMAIL = 'change#me.com'
TEMP_EMAIL_REGEX = /change#me.com/
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :provider , :uid
devise :omniauthable
def self.find_for_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
return registered_user
else
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email.blank? ? TEMP_EMAIL : auth.info.email,
password:Devise.friendly_token[0,20],
)
end
end
user
end
end
Here is my for devise and callback routes.rb:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
get '/auth/venmo/callback' => 'users/omniauth_callbacks#venmo'
login page link:
<%= link_to "Sign in with Venmo", user_omniauth_authorize_path(:venmo) %>
screenshot:
I'm not sure the exact issue here, but one thing to be cognizant of is it looks you're hitting a naked URL instead of api.venmo.com/v1/, which is our latest version. Try that first, Then we can work from there.

rails 3.2 devise google authentication

I have problem with authentication through devise + google. I have local application written in Ruby on Rails 3.2. I was following devise docs to make this authentication to works:
https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
So here is my users omniauth controller located in app/controllers/users
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
My routes:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
in my devise.rb initializer:
require "omniauth-google-oauth2"
config.omniauth :google_oauth2, "ClientID", "SecretId", { access_type: "offline", approval_prompt: "" }
My user model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:email => data["email"]).first
unless user
user = User.create(email: data["email"], password: Devise.friendly_token[0,20])
end
user
end
I don't know why this is not working. Maybe I'm doing something wrong with generating oauth clientid in google?
I generate my google app clientid and secret like in the following tutorial:
http://richonrails.com/articles/google-authentication-in-ruby-on-rails
Edit: I forgot to show what error I have:
when i click my link:
<%= link_to "Login via Google account", user_omniauth_authorize_path(:google_oauth2), :class => "btn btn-inverse btn-large" %>
i have the following addres in browser :
https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=487629202871.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&state=68a65048cca9ccb61a31d2a048bf9ef03d25ebe4a11d77ca&access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile
and my error looks like this:

Devise Test Helper - sign_in does not work

For some reason, I can not get the devise helper method sign_in to work. current_user keeps on being null. Any idea what the problem could be?
Test:
before :each do
#user = FactoryGirl.create :user
sign_in #user
end
describe "GET index" do
it "assigns all subscribers as #subscribers" do
subscriber = #user.subscribers.create! valid_attributes
get :index
assigns(:subscribers).should eq([subscriber])
end
end
Implementation:
def index
#subscribers = current_user.subscribers.all <------- ERROR
respond_to do |format|
format.html # index.html.erb
format.json { render json: #subscribers }
end
end
Error:
NoMethodError:
undefined method `subscribers' for nil:NilClass
Any help is appreciated. Thanks!
If you include the Confirmable module in your User model (or other devise-authenticatable model), then the test #user you create must be confirmed for the sign_in to take effect:
before :each do
#user = FactoryGirl.create :user
#user.confirm!
sign_in #user
end
(I see that this wasn't your issue, but perhaps another reader shall benefit from it.)
Looks like you solved this, judging by your code. I have had this happen before, and for some reason it gets me every time.
The rspec/rails scaffold for controller specs won't work with Devise::TestHelpers out of the box.
get :index, {}, valid_session
The valid_session call overwrites the session stuff that Devise sets up. Remove it:
get :index, {}
This should work!
For versions of Devise 4.2.0+, the Devise::TestHelpers have been deprecated. Instead, Devise::Test::ControllerHelpers should be used.
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
end
changelog
For the spec, make sure to include Devise::TestHelpers. To make it easy, in my spec/spec_helper.rb, I have:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
which automatically includes it for all controller specs.
Also, you need to do this to get sign_in to work:
#request.env["devise.mapping"] = Devise.mappings[:user]
get :new
It is probably best to add #request.env["devise.mapping"] = Devise.mappings[:user] to your before(:each). (Note you can do this in your config if you don't want to do this for every controller).
For the current_user part, make sure you have a model User, where you call devise
class User < ActiveRecord::Base
# call devise to define user_signed_in? and current_user
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# though you don't have to include all these modules
end
Devise uses the call in the User model to define user_signed_in? and current_user in your controllers. The reason is that if you have:
class Admin < ActiveRecord::Base
devise
end
then Devise will have methods admin_signed_in? and current_admin defined.
I encountered this problem when trying to test that an SSO endpoint I was writing was creating an session for a user. Since it only applied to one test, I just needed to add the following block before my test
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user, :email => email, :password => "password")
user.confirm!
end
it "should create and session for the user and redirect to home page" do

Resources