I recently added the Devise engine to a project with an existing authentication system (from the Rails Tutorial).
Before I added Devise, my UsersController handled all user registration facilities new, create, edit, destroy and I had a lot of rspec tests written for them. Of course now that registration is handled by Devise, I've had to remove those tests from users_controller_spec.rb.
Where can I put those tests to ensure that the Devise registration process is working correctly?
EDIT:
Ok I believe I've made some progress with this since I posted the question.
First I overrided the Devise registrations controller by making my own RegistrationsController in /app/controllers/registrations_controller.rb.
class RegistrationsController < Devise::RegistrationsController
end
Then I tell Devise to use this controller for registrations in my /config/routes.rb file:
devise_for :users, :controllers => { :registrations => "registrations" }
Now all I need to do it make my tests in /spec/controllers/registrations_controller_spec.rb work.
Am I on the right track?
That would be a good solution if you planned on overriding some of the controller methods. Typically, you don't test other frameworks like this that already have their own suite of tests. You are just duplicating the tests. That said, if you are more comfortable having your own, go for it.
Related
Gems
ruby 1.9.3
rails 3.2.11
devise 2.2.3
acts_as_tenant 0.2.9
Code
All my models are scoped by a domain_id:
class User < ActiveRecord::Base
acts_as_tenant(:domain)
#...
end
Then, in my application_controller, I set the current tenant from the domain:
class ApplicationController < ActionController::Base
set_current_tenant_through_filter
before_filter :set_tenant
protect_from_forgery
#...
def set_tenant
#...
#domain = Domain.find_or_create_by_name(request.host)
set_current_tenant(#domain)
end
end
All works well for all models except for sessions: Everytime a page is loaded, it will log-out the first user who load a page with another tenant. By loading this page, it will log-out the first user who [... etc.]
Hypothesis: when Alice visits a domain, Rails loads current_tenant=alice_domain (ok). All works as expected, until Bob visits another domain, load current_tenant=bob_domain. When Alice refreshes her page, Rails still has current_tenant==bob_domain. Rails checks session: Alice does not exist with bob_domain scope, so Devise forces Alice logout. Then application_controller sets current_tenant=alice_domain... which logs-out Bob.
Dirty workaround: do not use acts_as_tenant in user model, scope users by domain myself in every controllers, then overwrite devise to scope login and registration by domain. And I'm unsure how to get Devise aware of current domain in sessions stuff. By the way, replacing acts_as_tenant by a manual default_scope in user falls in the same strange bugs. It seems very dirty to go this way.
I'm looking for a clean solution for days. I would be very grateful for any help.
Fixed, in application_controller, change
before_filter :set_tenant
to
prepend_before_filter :set_tenant
in order to default_scope everything, including User, before Devise checks the user's session.
I am working on an app where a company admin should be able to create and update users in his company. (Users can also create and update their accounts independently).
I used Devise and Cancan for registration and permission management. I configured Devise to get the required signup and user update processes. I created a namespace for the admin views.
My admin controller (/app/controllers/admin/base_controller.rb) looks like this:
class Admin::BaseController < ApplicationController
authorize_resource :class => false
layout 'admin'
def dashboard
end
end
In addition to my "regular" users_controller, I have a controller (/app/controllers/admin/users_controller.rb) and associated views dedicated to Admin user management.
class Admin::UsersController < Admin::BaseController
...
end
Now, what's the cleanest way to implement Devise-related user admin features (at this point, create and update users)?
Have a conditional (based on user permissions) in my "regular" registrations_controller and confirmations_controller to render and redirect different views? (cf. Devise form within a different controller)
Or create new controllers in my admin namespace? If the latest is better, what are the main steps to follow?
Herein lies your issue, once you namespace or move any user management to another controller, you leave the scope of Devise. So at the point where you are in Admin::UsersController, Devise doesn't care about what you do, there are no 'devise-related' admin features as you stated. You can implement your Admin::UsersController as a standard RESTful controller if you wish.
In this manner, creating users through a namespaced controller, Devise will still perform actions such as confirmations. There is one small thing to keep in mind when creating and updating users this way. If you do not set a password as the admin, you will have to delete the password and password_confirmation from the params hash. To do so, the start of your create and update actions would look like so:
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
I employ this same method in many of my applications, and have yet to have it fail.
EDIT
namespace :admin do
resources :users
end
I have a Rails app running Rails 3.2.6 and Devise 2.1.2 and I want to be able to limit concurrent logins to 1. I've tried using the Devise Security Extension but keep getting an undefined method for unique_session_id.
Instead I want to see if I can override Devise's behavior as in this SO article: devise limit one session per user at a time
I understand how it works and I've generated a sessions controller, but I'm not sure how to force the app to use it as an override for Devise. Also in the controller should I be inheriting from the Application Controller or the Devise Controller?
Everything in place right now however the app doesn't write a login_token which tells me that I'm not using the controller properly or something else might be amiss.
Any help is appreciated.
I would strongly recommend you check out the Devise Wiki. Chances are when doing something like this, the answer can be found there. Your new controller will need to inherit from the Devise Sessions Controller as such:
class MySessionsController < Devise::SessionsController
This way, any methods you did not specifically override will still be called, thus not breaking other functionality.
In order to make sure that Devise uses your controller instead of it's own, make your devise_for call in routes.rb look as follows:
devise_for :users, :controllers => { :sessions => "my_sessions" }
From there Devise will pick up your controller. It's probably a good idea to restart your server in there just to be sure.
Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.
I'm working on a Rails 3 web app at the moment with RSpec 2 and we're using Devise for authentication. One (and soon many) of our controllers require the user to be logged in. I know Devise provides the sign_in test helper, but can it be used with an RSpec or Mocha mock object?
I originally tried #user = mock_model(User) where user is the Devise class. This wouldn't work with sign_in :user, #user as get 'index' would redirect to the sign in form.
Does anyone have any experience testing with Devise and can help?
We had a similar problem, but using Factory Girl. We solved it like so:
In spec_helper.rb:
config.include Devise::TestHelpers, :type => :controller
In the controller spec (just a wrapper method):
def login_user(user)
sign_in user
end
Then in each method you require, you can do:
login_user(Factory(:user))
... where you have defined a user object in factories.rb. Not sure if this will work with mocks though.
A mock is never going to work. When you say sign in, the user is stored in session (basically, the user class and its id). When you access the controller, another user object is retrieved based on the stored data. The best way to solve the problem is using something that persists the object, like Factory Girl.
I hit the same issue. I'm doing the following for now:
before(:each) do
# sign_in mock_user
request.env['warden'] = mock(Warden, :authenticate => mock_user,
:authenticate! => mock_user)
end
I've created an issue for this here: https://github.com/plataformatec/devise/issues#issue/928
Go vote!
None of them worked for me (MRI 1.9.3-preview1, rails 3.0.1.rc5).
this is the solution i found : http://blog.joshmcarthur.com/post/6407481655/integration-tests-with-devise-and-rspec