I am trying to override a default scope set up in an omniauth gem. I've tried setting the scope via the initializer as well as trying to leave the scope blank. I want to be able to do
/auth/<provider>?scope=<scope>
as a link for signing in.
However, regardless of how I setup the provider in
config/initializers/omniauth.rb
ex:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :<provider>, <app_uid>, <app_secret>,
:scope => "",
:client_options => {
:site => "https://api.<provider_url>",
:authorize_url => "https://api.<provider_url>/oauth/authorize",
:token_url => "https://api.<provider_url>/oauth/token"
}
end
it still tries to send an omniauth / oauth-2 request using the default scope setup in the
omniauth-<provider> gem
How can I override the default scope declared in the gem via passing in a scope param to the url being passed to the provider?
I was able to do so using the following initializer.
Rails.application.config.middleware.use OmniAuth::Builder do
:<provider>, <app_uid>, <app_secret>,
:setup => lambda { |env| env["omniauth.strategy"].options[:scope] = env["rack.session"]["omniauth.params"]["scope"]},
:client_options => {
:site => "https://api.<provider_url>",
:authorize_url => "https://api.<provider_url>/oauth/authorize",
:token_url => "https://api.<provider_url>/oauth/token"
}
end
Related
My Rails 3.2 app uses OmniAuth and Devise to sign in with Twitter. The authentication system works fine. I would like to write an integration test in rspec to make sure everything works. Using the information in the wiki, I've written the following, but I know I'm missing things.
Under test.rb in config/environments, I have the following lines
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = {:provider => 'twitter', :uid => '123545'}
My rspec test looks like this:
describe "Authentications" do
context "without signing into app" do
it "twitter sign in button should lead to twitter authentication page" do
visit root_path
click_link "Sign in with Twitter"
Authentication.last.uid.should == '123545'
end
end
end
Authentication is the name of my model and calling .uid in rails console returns the string fine.
I'm getting the following error when I run this test:
Failure/Error: Authentication.last.uid.should == '123545'
NoMethodError:
undefined method `uid' for nil:NilClass
Can anyone help me figure out how to use the OmniAuth mocks that are provided? An explanation for why and how it works would be appreciated as well.
I run into something similar.
After changing my mock object from using symbol keys:
OmniAuth.config.mock_auth[:twitter] = {
:uid => '1337',
:provider => 'twitter',
:info => {
:name => 'JonnieHallman'
}
}
to using string keys:
OmniAuth.config.mock_auth[:twitter] = {
'uid' => '1337',
'provider' => 'twitter',
'info' => {
'name' => 'JonnieHallman'
}
}
it worked.
And do you have
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
somewhere in your testcase?
Did you try moving these two lines to spec_helper.rb?
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = {:provider => 'twitter', :uid => '123545'}
Also add the following before block in your test file:
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
You can find more info on this link: https://github.com/intridea/omniauth/wiki/Integration-Testing
I highly suggest this answer
In short...
Set up the mock
Make the request
Test whatever code is attached to the callback
For example: test the session['uid'] was set (although, I opt to test only what the user sees, or, rather, does not see)
My code...
config/environments/test.rb
Rails.application.configure do
...
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:linkedin] = {
'provider' => 'linkedin',
'uid' => '123545',
'info'=>
{ 'email'=>'infinite#jest.com',
'first_name'=>'Dave',
'last_name'=>'Wallace' }
}
end
spec/features/sign_in_feature_spec.rb
require 'rails_helper'
feature 'Sign in with LinkedIn' do
before do
OmniAuth.config.add_mock(:linkedin, {:uid => '12345'})
end
let(:user) { create(:user) }
scenario 'with valid email and password' do
visit '/'
expect(page).to have_no_content 'Sign Out'
click_link 'nav-sign-in' # image/button: Sign in with LinkedIn
expect(page).to have_content 'Sign Out'
end
end
Let me know if/how I may improve this solution (and my code!)
Selected solution does not work for me.
My solution i get from https://gist.github.com/kinopyo/1338738
and official doc https://github.com/intridea/omniauth/wiki/Integration-Testing
here:
# in spec/support/omniauth_macros.rb
module OmniauthMacros
def mock_auth_hash
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[:odnoklassniki] = OmniAuth::AuthHash.new({
:provider => 'odnoklassniki',
:uid => '123545',
:info => OmniAuth::AuthHash::InfoHash.new({
:name => 'mockuser'
})
})
end
end
# in spec/spec_helper.rb
RSpec.configure do |config|
# email spec
config.include(EmailSpec::Helpers)
config.include(EmailSpec::Matchers)
end
OmniAuth.config.test_mode = true
# in spec example:
visit new_user_registration_path
mock_auth_hash
find('#btn-odnoklassniki').click # here is link generated as omniauth_authorize_path(resource_name, provider)
I'm looking for a simple example for a omniauth-facebook, devise, capybara, capybara-mechanize, (maybe with VCR), and rpsec integration test...
Capybara can't connect to to an external website. So capybara-mechanize would be needed. Maybe VCR could speed things up. This link describes how to get test facebook users: https://developers.facebook.com/docs/test_users/
Using a mock, as described in the omniauth docs works well for testing the logic of the model and controller. This requires the following code in specs/helpers/omniauth.rb and calling set_omniauth in the :before of your rspec integration test.
However, it would be nice to have a true integration test that runs against facebook servers. Surely, you'd want to flag such slow tests so they do not run via guard and the default rake task.
# You can read about this gist at: http://wealsodocookies.com/posts/how-to-test-facebook-login-using-devise-omniauth-rspec-and-capybara
# which is for twitter. Below is for facebook
def set_omniauth(opts = {})
default = {:provider => :facebook,
:uuid => "1234",
:facebook => {
:email => "foobar#example.com",
:gender => "Male",
:first_name => "foo",
:last_name => "bar",
:nickname => "foo bar",
:image => 'http://graph.facebook.com/659307629/picture?type=square'
}
}
credentials = default.merge(opts)
provider = credentials[:provider]
user_hash = credentials[provider]
OmniAuth.config.test_mode = true
h = {
'provider' => provider,
'uid' => credentials[:uuid],
'info' => user_hash,
"extra" => {
"info" => user_hash
}
}
OmniAuth.config.mock_auth[provider] = CollectionUtility.deep_stringify_keys(h)
end
def set_invalid_omniauth(opts = {})
credentials = { :provider => :facebook,
:invalid => :invalid_crendentials
}.merge(opts)
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[credentials[:provider]] = credentials[:invalid]
end
I'm trying to write tests for authentication with Twitter and Facebook. I am using Devise and Omniauth. When I try it out it works correctly, but I can't get the tests to pass for it.
I'm following instructions, but it isn't in depth enough for me, plus I'm doing things a little differently (already have some code base). I have a custom controllers for omniauth_callbacks and registrations.
My problem is that when I run the tests it says:
(::) failed steps (::)
No route matches "/oauth/authorize" (ActionController::RoutingError)
<internal:prelude>:10:in `synchronize'
(eval):2:in `click_link'
./features/step_definitions/web_steps.rb:57:in `/^(?:|I )follow "([^"]*)"$/'
features/link_twitter.feature:19:in `And I link twitter'
Failing Scenarios:
cucumber features/link_twitter.feature:16 # Scenario: User links twitter
Where is this /oauth/authorize route coming from and how do I handle that?
It does not look like it is getting to my OmniauthCallbacksController. It follows the link and then dies. I think it has to do with Omniauth's call back method during test mode, but I'm not sure how to change / manage that.
Update: Here are my routes for Devise.
devise_for :users, :controllers => {
:omniauth_callbacks => "users/omniauth_callbacks",
:registrations => 'registrations',
:passwords => 'passwords',
:sessions => 'sessions',
:confirmations => 'confirmations'
} do
match 'confirmations/unconfirmed' => 'confirmations#unconfirmed', :as => :unconfirmed
match 'confirmations/send_advisor_confirmation/:id' => 'confirmations#unregistered_advisor_confirmation', :as => :send_advisor_confirmation
get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru'
end
I forgot to put the following into env.rb
# features/support/env.rb
OmniAuth.config.test_mode = true
For more information read about the testing here.
Consider this as a challenge rather than its general approach. The reason I mention this is because, it is generally preferred to incorporate admin-accessible features into the public facing site. This is what's required:
Devise model for Users, visitors accessing the public facing site
Devise model for Admins
Namespace or scope the admin 'area' to /admin. Admins can only login from this route.
Users can sign up directly from the site's public facing landing page; they are not forced to visit /users/sign_up as per the default devise generated route.
Consider overriding the default devise controllers
Thanks,
Mike.
The following seems like I've made some progress in the right direction; this at least provides identical out-of-the-box devise functionality for both users and admins, with the custom routing,
match 'admin', :controller => 'admin'
namespace :admin do
# to be updated later...
end
devise_for :users
#devise_for :admins, :path => "admin" # this works but uses the default
# Devise::SessionController
devise_for :admins,
:controllers => {
:sessions => "admin/sessions",
:passwords => "admin/passwords",
:registrations => "admin/registrations" }, :path => "admin",
:skip => [:sessions, :passwords, :registrations] do
get 'admin/sign_in' => 'admin/sessions#new', :as => :new_admin_session
post 'admin/sign_in' => 'admin/sessions#create', :as => :admin_session
get 'admin/sign_out' => 'admin/sessions#destroy', :as => :destroy_admin_session
get 'admin/sign_up' => 'admin/registrations#new', :as => :new_admin_registration
get 'admin/account' => 'admin/registrations#edit', :as => :edit_admin_registration
post 'admin/account' => 'admin/registrations#create', :as => :admin_registration
get 'admin/cancel' => 'admin/registrations#cancel', :as => :cancel_admin_registration
put 'admin/account' => 'admin/registrations#update'
delete 'admin/account' => 'admin/registrations#destroy'
post 'admin/password' => 'admin/passwords#create', :as => :admin_password
get 'admin/password/new' => 'admin/passwords#new', :as => :new_admin_password
get 'admin/password/edit' => 'admin/passwords#edit', :as => :edit_admin_password
put 'admin/password' => 'admin/passwords#update'
end
Ideas?
caveat: in this example, I've included the :registerable devise module in the Admin model just for testing during development. The sign_up route will, ultimately, be removed.
Much searching yielded (mind the pun) the following blog post that seems to indicate overriding a devise controller requires the re-mapping of all its specified 'HTTP verbs' as it were; this makes sense as unmapped ones would be handled by the default devise controller.
If anyone has more experience working with multiple devise models and the separated admin approach, I would be very much interested in your thoughts and suggestions!
This function is defined in the application_help.rb:
def gravatar_url_for(email, options = {})
url_for(
{
:protocol => 'http://',
:host => 'www.gravatar.com',
:controller => 'avatar',
# :controller => 'avatar.php',
:gravatar_id => Digest::MD5.hexdigest(email),
:only_path => false
}.merge(options)
)
end
It's used in views:
<%= image_tag(gravatar_url_for user.email, {:d => 'identicon', :s => 32, :r => 'g'}) %>
Occasionally, its usage will result in a routing error:
No route matches {:controller=>"avatar", :d=>"identicon", :r=>"g", :gravatar_id=>"486575e581db04b7c8ca218af8488657", :s=>32}
A valid email is being supplied when the error occurs.
If I replace the url_for() with this logic, it works as expected:
url_for("http://www.gravatar.com/avatar/" + Digest::MD5.hexdigest(email) + "?d=identicon&s=40&r=g")
** edit **
I had removed the following line from the routes.rb file:
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
match ':controller(/:action(/:id(.:format)))'
Is there a way to get the url_for to work without the 'legacy wild controller route'?
You might want to take a look at the Gravtastic plugin for Rails which supports Gravatar images in both Ruby and JavaScript.