Rails 3.2 - Application Controller + Devise Current_user Issue - authentication

I have two roles defined in my User Model:
User.role == admin
or
User.role == basic
In my Application Controller:
include MobilizedController
In lib/mobilized_controller.rb:
module MobilizedController
extend ActiveSupport::Concern
included do
before_filter :set_basic_request, :if => :basic_logged_in?
end
private
def set_basic_request
request.format = :basic
prepend_view_path "app/views/basic"
end
def basic_logged_in?
current_user.role == 'basic'
end
end
This is all fine when I am logged in, it sets the Mime Type correctly and renders my basic views.
Unfortunately, when I'm not logged in I get:
Undefined method 'role' for nil:NilClass
which I guess means that the current_user is not set, and thus calling nil on it won't work.
Does anyone have any suggestion for this? I need to be able to render the default log in page if a user is not logged in, and then set the Mime Type on login.
Any help?

In your example for basic_logged_in you set current_user.role to basic. It seems like you do not set a role for admin or not-logged in users.
So current_user.role is not set if you are not logged in or login as admin.

Related

Sign in redirects to home page instead of staying on same page

I installed devise and it is working properly but the sign in redirect. When user signs in from any page of app, Devise redirects user to home instead of redirecting on same page. I tried implementing How_to solutions of devise but no help.
I wrote in application_controller:
def after_sign_in_path_for(resource)
current_user_path
end
But it gives error:
undefined local variable or method `current_user_path' for #<Devise::SessionsController:0xaeacc34>
When i write:
def after_sign_in_path_for(resource)
current_user_path
end
It gives :
undefined method `user_url'
Whats the solution for this problem? Can any body help in this?
You can use request.referer
def after_sign_in_path_for(resource_or_scope)
request.referer
end
You need to redirect to somewhere else in which scenerio(error or success)? Usually after_sign_in_path_for used after user logged in successfully. SO:
if you haven't override devise default functionality then use below code for navigate control to specific custom page. current_user is also accessible in this action.
def after_sign_in_path_for(resource)
scope = Devise::Mapping.find_scope!(resource)
scope_path = :"#{scope}_root_path"
respond_to?(scope_path, true) ? send(scope_path) : root_url
end
Update:
Another example is as follows:
def after_sign_in_path_for(resource_or_scope)
current_user.admin? ? dashboard_admin_home_index_path : current_user.sign_in_count >= 1 ? "/home/dashboard" : "/#{current_user.role}/dashboard"
end

Devise & Rspec: User requires account activation in requests spec even after confirmed

I have the following code in my requests spec:
describe 'Poll' do
subject { page }
context 'as system admin' do
let(:user) { Fabricate(:system_admin) }
before { login user}
it 'is accessible' do
visit '/admin/poll'
current_path.should == '/admin/poll'
end
describe 'sending poll' do
it 'sends to all users' do
save_and_open_page
end
end
end
end
The login user doesn't seem to work even if the method seems to be working fine. I tried using login user inside the it 'is accessible' do block and that specs works fine if I do it that way. If I remove it from there and put it in a before block like above. The user doesn't stay signed in. I put in a save_and_open_page to debug and I get this notification in the page:
Your account was not activated yet. If a reset password link was sent to you, use that link to change your password.
I'm using Devise, RSpec, Capybara and Rails 3. I've also set user to confirm! in my Fabrication file. Below is how it looks:
Fabricator(:system_admin) do
first_name { sequence(:first_name) { |n| "Person#{n}"} }
last_name { sequence(:last_name) {|n| "#{n}" } }
email { sequence(:email) { |n| "person_#{n}#example.com"} }
password "foobar"
password_confirmation "foobar"
company_name { sequence(:company_name) { |n| "google#{n}" } }
role "system_admin"
after_create do |user|
user.confirm!
user.create_company
end
end
Question: What could be the problem? How come the user isn't staying logged in and why do I get that message saying that I should activate my account? Isn't user.confirm! enough?
could this be the problem?
Fabricate(:system_admin) != Fabricator(:system_admin)
So if you debug your save_and_open_page and it tells you that the account is not activated it seemes your fabricate is not working properly. have you tried and debug that?
what does your save_and_open_page do? does it try to use the user for something? because I have experienced when defined with a let, if not touched the variable(user in this case) then it does no exist on that context. besides. whats the error when you run the specs like this on it "is acessible"? just says there is no user logged in?
so you can either stub your methods for login(for example if you have method called current_user that gives you the logged in user or something) or instead of using let, instiate like:
user = Fabricate(:system_admin)
but hey there is a lot of good advices here:
http://betterspecs.org/
it seems like your blocks context and describe are too complex. I am also not following this guidelines 100% but I think I should and you too would benefit from this.
let me know if you found out another reason why its not working!
I think before(:each) should resolve the problem
Add this Devise method:
confirmed_at { Time.now }
So your after_create method should looks like:
after_create do |user|
user.confirm!
user.confirmed_at { Time.now }
user.create_company
end

Rails Sorcery Bug? Creates Duplicate User Accounts

The example sorcery code shown on github appears to me to create duplicate accounts if it is extended to allow for multiple sign in methods (which is the whole point of oauth). You can see in the snipit here that create_from() will be called if login_from() does not succeed.
GITHUB AT at https://github.com/NoamB/sorcery-example-app/blob/master/app/controllers/oauths_controller.rb
def callback
provider = params[:provider]
begin
if #user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
else
begin
#user = create_from(provider)
Investigating the source code for create_from in all cases a new User Account record will be created. This would not be correct, if a User account record already exists.
My question: What sorcery methods should be called on the first facebook connect, if a User account has been created by some means other than facebook. login_from will fail, and create_from will generate a duplicate usser record?
You can use def create_and_validate_from(provider).
It will validate if the users email/username already exist. If its true, that he will store infos into a session and can be rendered into registration form.
And if you wish to add some provider to your account you can use def add_provider_to_user(provider).
Several requests have come through for an answer to this question, so I am providing the answer that Andy Mejia part of my team eventually arrived at for this question. We used the source within sorcery to adapt the following functions:
# Returns the hash that contains the information that was passed back from Facebook.
# It only makes sense to call this method on the callback action.
#
# Example hash:
# {:user_info=>{:id=>"562515238", :name=>"Andrés Mejía-Posada", :first_name=>"Andrés", :last_name=>"Mejía-Posada", :link=>"http://www.facebook.com/andmej", :username=>"andmej", :gender=>"male", :email=>"andmej#gmail.com", :timezone=>-5, :locale=>"en_US", :verified=>true, :updated_time=>"2011-12-31T21:39:24+0000"}, :uid=>"562515238"}
def get_facebook_hash
provider = Rails.application.config.sorcery.facebook
access_token = provider.process_callback(params, session)
hash = provider.get_user_hash
hash.merge!(:access_token => access_token.token)
hash.each { |k, v| v.symbolize_keys! if v.is_a?(Hash) }
end
# Method added to the User Account model class
def update_attributes_from_facebook!(facebook_hash)
self.first_name = facebook_hash[:user_info][:first_name] if self.first_name.blank?
self.last_name = facebook_hash[:user_info][:last_name] if self.last_name.blank?
self.facebook_access_token = facebook_hash[:access_token]
self.email ||= facebook_hash[:user_info][:email]
unless facebook_authentication?
authentications.create!(:provider => "facebook", :uid => facebook_hash[:uid])
end
self.build_facebook_profile if facebook_profile.blank?
save!
self.facebook_profile.delay.fetch_from_facebook! # Get API data
end
To show these code in context, I am also including logic from our controller:
def callback
provider = params[:provider]
old_session = session.clone # The session gets reset when we login, so let's backup the data we need
begin
if #user = login_from(provider) # User had already logged in through Facebook before
restore_session(old_session) # Cleared during login
else
# If there's already an user with this email, just hook this Facebook account into it.
#user = UserAccount.with_insensitive_email(get_facebook_hash[:user_info][:email]).first
# If there's no existing user, let's create a new account from scratch.
#user ||= create_from(provider) # Be careful, validation is turned off because Sorcery is a bitch!
login_without_authentication(#user)
end
#user.update_attributes_from_facebook!(get_facebook_hash)
rescue ::OAuth2::Error => e
p e
puts e.message
puts e.backtrace
redirect_to after_login_url_for(#user), :alert => "Failed to login from #{provider.titleize}!"
return
end
redirect_to after_login_url_for(#user)
end
I hope this solution is helpful to others.
I came across the same problem. While I have not found a direct solution via Sorcery, I did the following which seems to work:
#user = create_from(params[:provider]) do |user|
User.where(:twitter_id => user.twitter_id).first.blank?
end
This teqnique requires that you have twitter_id in the User model. You can also do it the other way around with the Authentication model instead. Such as:
#user = create_from(params[:provider]) do |user|
Authentication.where(:uid => user.twitter_id).first.blank?
end
If the block returns false, then it doesn't create the user. Avoiding any duplicates.
Note, the block for create_from does not work with 0.7.12. It works with 0.7.13.

Devise and stored_location_for: how do you store the return location?

I have a page whose path is (e.g.) /premises/92 on which I'm displaying "please [log in] or [register] for additional information" if the user is not logged in, and I want devise to return that same /premises/92 page after the user logs in.
I've read other posts and I think I understand how devise's stored_location_for is supposed to work. In theory, I could put something like this in my ApplicationController:
def stored_location_for(resource)
if (r = session[:return_to])
session[:return_to] = nil
r
else
super
end
end
My question is: how / where do I set up session[:return_to]?
I want to set session[:return_to] only if the user clicks on [log in] or [register], but what's the best way to do that?
Decorate the links with JavaScript? That could work, but seems heavy-handed.
Set it in the Premises Controller before rendering the page? That doesn't seem right: what if the user doesn't click on the [log in] or [register] links? Then I have session[:return_to] set to some odd value which might trip me up if the user logs in from some other page.
Add a ?return_to=/premises/92 query string to the [log in] and [register] links, and detect that in the RegistrationsController and SessionsController and use that info to set up session[:return_to]? That seems like it would work, but also heavy-handed.
None of these smell right. What's the generally accepted technique for setting up state for stored_location_for?
Devise use
session["#{scope}_return_to"]
So you can use session["user_return_to"] if your model for authentication is User.
I found this whole devise redirect thing quite confusing.
Where #rorra says Devise uses session["#{scope}_return_to"], he means that Devise default after_sign_in_path_for(resource) will use that variable through the method stored_location_for(resource).
So you should save the location you want to store in the variable session["#{scope}_return_to"], which will usually be session["user_return_to"]. To do this place the following in your application_controller.rb:
after_action :store_location
def store_location
# store last url - this is needed for post-login redirect to whatever the user last visited.
if (request.fullpath != "/users/sign_in" &&
request.fullpath != "/users/sign_up" &&
request.fullpath != "/users/password" &&
request.fullpath != "/users/sign_out" &&
!request.xhr?) # don't store ajax calls
session["user_return_to"] = request.fullpath
end
end
In some cases you will not need to define an after_sign_in_path_for(resource) method as devise's default method will do all the redirects for you and if their is no redirect url available you will be redirected to the resource root path (usually user root path) and if that doesn't exist you will be redirected to the root path. If however, you would like to customise where the user is sent if there is no redirect url available, add the following to your application_contorller.rb (and change root_path accordingly):
def after_sign_in_path_for(resource)
stored_location_for(resource) || root_path
end
Devise looks for the key "#{resource}_return_to" in the session.
Here I am quoting the API:
By default, it first tries to find a valid #{resource}_return_to key in
the session, then it fallbacks to #{resource}_root_path, otherwise it
uses the root_path.
Here is the link to the API
here's the best I could come up with. Works perfectly also with facebook authentication. by adding more restrictions to the prepending of urls to the session variable you can remove more and more paths you don't want the user to return too (e.g. callbacks, splash pages, landing pages, etc)
#ApplicationsController
after_filter :store_location
def store_location
session[:previous_urls] ||= []
# store unique urls only
session[:previous_urls].prepend request.fullpath if session[:previous_urls].first != request.fullpath && request.fullpath != "/user" && request.fullpath != "/user/login" && request.fullpath != "/" && request.fullpath != "/user/logout" && request.fullpath != "/user/join" && request.fullpath != "/user/auth/facebook/callback"
# For Rails < 3.2
# session[:previous_urls].unshift request.fullpath if session[:previous_urls].first != request.fullpath
session[:previous_urls].pop if session[:previous_urls].count > 3
end
def after_sign_in_path_for(resource)
#url = session[:previous_urls].reverse.first
if #url != nil
"http://www.google.com" + #url
else
root_path
end
end
I do like this:
class ApplicationController < AC::Base
after_filter :clear_attached_unit # UPDATED
protected
def clear_attached_unit
session[:attached_unit_path] = nil unless keep_attached_unit_path?
end
def keep_attached_unit_path? # UPDATED
#keep_attached_unit_path
end
end
class UnitController < ApplicationController
before_filter :attach_unit, :only => [:show]
protected
def attach_unit
session[:attached_unit_path] = request.url if request.get? && !request.xhr?
end
end
class SessionsController < Devise::SessionsController
before_filter :keep_attached_unit_path! # UPDATED
protected
def keep_attached_unit_path! # UPDATED
#keep_attached_unit_path = true
end
def after_sign_in_path_for(resource_or_scope)
if resource_or_scope.is_a?(User) && session[:attached_unit_path].present?
session[:attached_unit_path]
else
super
end
end
end
And extract this to module.

Disabling sessions for a single controller has been deprecated

The problem is;
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_simple_blog'
#session :disabled => true
private #------------
def authorize_access
if !session[:user_id]
flash[:notice] = "Please log in."
redirect_to(:controller => 'staff', :action => 'login')
return false
end
end
end
the error message is
DEPRECATION WARNING: Disabling sessions for a single controller has been deprecated. Sessions are now lazy loaded. So if you don't access them, consider them off. You can still modify the session cookie options with request.session_options.
Can somebody point em in the right direction.
Thanks
You are receiving this warning because you are explicitly loading the session context via the session method. You should instead use request.session_options[:session_key] = 'new_session_key' from within an action, as the framework now lazily loads the context if necessary (as you saw). If you want to do this for all actions, create a method and use before_filter:
class ApplicationController < ActionController::Base
before_filter :setup_session_key
protected
def setup_session_key
# Pick a unique cookie name to distinguish our session data from others'
request.session_options[:session_key] = '_simple_blog'
end
end