I use devise for authentication. In my User model there is a field "locale", which I want to use in the application controller for the current logged in user. So I have this in the application controller:
#user = User.current_user.locale
But this doesn't work. How can I access fields of the current user in the application controller? And devise hasn't created a users controller, where can I find that when I want to make some changes there? Thanks!
current_user is already User object, just call locale on it :
#user = current_user
locale = current_user.locale
current_user is an instance method, meaning you can only call it on (or within) an instance of the User class. When you do User.current_user you're trying to call it as a class method, i.e. a method on the User class itself.
If you really need to use current_user within ApplicationController--at best a code smell and at worst a really bad idea--you can fall back on Warden, which Devise is built on top of. E.g.:
user = env['warden'].current_user
user.locale
As for your other question regarding controllers, it has been answered before.
current_user is available as usual in the application controller.
to access it in every request (in your application controller):
before_filter :get_locale
def get_locale
#user = current_user
locale = #user.locale
end
current_user is not available in application_controller as it extends base.
Simple solution is to use it like that
before_action :user_full_name
def user_full_name
#user_full_name = current_user.fname + ' ' + current_user.lname
end
Related
I have functionality of inactive account in my application for handling this i override active_for_authentication? method as below
def active_for_authentication?
super && activated?
end
But In my application super admin can also directly login in to other user account, whether it is active or not active
bypass_sign_in(User.find(resource.id))
I used above method for by pass sign in, it allows me to directly sign in only for activated user, when i login for non activated user it goes in infinite loop .
Any solutions to over come this issue or don't run active_for_authentication? callback when bypass_sign_in?
When admin logs in to another user account you can store some additional data in session, that makes it clear that this is the super admin mode.
def login_as(another_user)
return unless current_user.super_admin?
session[:super_admin_mode] = true
bypass_sign_in(another_user)
end
Unfortunately, you can't access session in Rails models, but you can store needed session information in some per-request global variable that is available in models. The solution might be like this:
module SessionInfo
def self.super_user_mode?
!!Thread.current[:super_user_mode]
end
def self.super_user_mode=(value)
Thread.current[:super_user_mode] = value
end
end
In the ApplicationController:
class ApplicationController < ActionController::Base
before_filter :store_session_info
private
def store_session_info
SessionInfo.super_user_mode = session[:super_admin_mode]
end
end
In the model:
def active_for_authentication?
super && (activated? || SessionInfo.super_user_mode?)
end
Also, you should make sure that the :super_admin_mode flag is removed from session when the super user logs out. Maybe it happens automatically, I am not sure. Maybe you will need to do it manually overriding Devise::SessionsController#destroy method (see the example below)
def destroy
session[:super_admin_mode] = nil
super
end
Also read this for better understanding of how devise handles session Stop Devise from clearing session
I recently came across a similar issue where I needed to allow an Admin to sign in as regular Users who were not active in Devise. I came up with the following solution that doesn't involve using Thread.current (which after looking into further online it seems like using Thread.current could be a precarious solution to this problem).
You can create a subclass of User called ProxyUser that has the active_for_authentication? return true. Something like this:
class ProxyUser < User
# If you have a type column on User then uncomment this line below
# as you dont want to expect ProxyUser to have type 'ProxyUser'
#
# self.inheritance_column = :_type_disabled
devise :database_authenticatable
def active_for_authentication?
true
end
end
Then in the controller you want something like this:
proxy_user = ProxyUser.find(params[:user_id])
sign_in :proxy_user, proxy_user
Also in your routes you will need devise to expect ProxyUser so include:
devise_for :proxy_users
And finally when you sign this user out (assuming you can sign the user out in your controller code) make sure to tell devise the scope of the sign out, so you would do
sign_out :proxy_user
And then finally note that in your app you may be expecting current_user in different places (such as if you use CanCanCan for authorization) and now when you sign in as a proxy_user your app will return current_user as nil. Your app will instead have an object called current_proxy_user that will be your signed-in ProxyUser object. There are many ways to handle the issues resulting from your current_user returning nil in this case (including overwriting current_user in your application controller).
With Ruby on Rails, my models are being created with increasing unique ids. For example, the first user has a user id of 1, the second 2, the third 3.
This is not good from a security perspective because if someone can snoop on the user id of the last created user (perhaps by creating a new user), they can infer your growth rate. They can also easily guess user ids.
Is there a good way to use random ids instead?
What have people done about this? Google search doesn't reveal much of anything.
I do not consider exposing user IDs to public as a security flaw, there should be other mechanisms for security. Maybe it is a "marketing security flaw" when visitors find out you do not have that million users they promise ;-)
Anyway:
To avoid IDs in urls at all you can use the user's login in all places. Make sure the login does not contain some special characters (./\#? etc.), that cause problems in routes (use a whitelist regex). Also login names may not be changed later, that can cause trouble if you have hard links/search engine entries to your pages.
Example calls are /users/Jeff and /users/Jeff/edit instead of /users/522047 and /users/522047/edit.
In your user class you need to override the to_param to use the login for routes instead of the user's id. This way there is no need to replace anything in your routes file nor in helpers like link_to #user.
class User < ActiveRecord::Base
def to_param
self.login
end
end
Then in every controller replace User.find by User.find_by_login:
class UsersController < ApplicationController
def show
#user = User.find_by_login(params[:id])
end
end
Or use a before_filter to replace the params before. For other controllers with nested resources use params[:user_id]:
class UsersController < ApplicationController
before_filter :get_id_from_login
def show
#user = User.find(params[:id])
end
private
# As users are not called by +id+ but by +login+ here is a function
# that converts a params[:id] containing an alphanumeric login to a
# params[:id] with a numeric id
def get_id_from_login
user = User.find_by_login(params[:id])
params[:id] = user.id unless user.nil?
end
end
Even if you would generate random INTEGER id it also can be compromted very easy. You should generate a random token for each user like MD5 or SHA1 ("asd342gdfg4534dfgdf"), then it would help you. And you should link to user profile with this random hash.
Note, this is not actually the hash concept, it just a random string.
Another way is to link to user with their nick, for example.
However, my guess is knowing the users ID or users count or users growth rate is not a vulnerability itself!
Add a field called random_id or whatever you want to your User model. Then when creating a user, place this code in your UsersController:
def create
...
user.random_id = User.generate_random_id
user.save
end
And place this code in your User class:
# random_id will contain capital letters and numbers only
def self.generate_random_id(size = 8)
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
key = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
# if random_id exists in database, regenerate key
key = generate_random_id(size) if User.find_by_random_id(key)
# output the key
return key
end
If you need lowercase letters too, add them to alphanumerics and make sure you get the correct random number from the kernel, i.e. Kernel.rand(62).
Also be sure to modify your routes and other controllers to utilize the random_id instead of the default id.
You need to add a proper authorization layer to prevent un-authorized access.
Let us say you you display the user information in show action of the Users controller and the code is as shown below:
class UsersController < ActionController::Base
before_filter :require_user
def show
#user = User.find(params[:id])
end
end
This implementation is vulnerable to id guessing. You can easily fix it by ensuring that show action always shows the information of the logged in user:
def show
#user = current_user
end
Now regardless of what id is given in the URL you will display the current users profile.
Let us say that we want to allow account admin and account owner to access the show action:
def show
#user = current_user.has_role?(:admin) ? User.find(params[:id]) : current_user
end
OTH authorization logic is better implemented using a gem like CanCan.
My Rails 3.2 project has a devise-generated user and a set of models that all contain data that's specific to that user. I want a logged-in user to be able to access only his own data through the APIs exposed by the controllers.
Now, a brute-force way to enable this would be to change each and every controller from something like:
def index
#stuff = Stuff.all
to
def index
#stuff = Stuff.find_all_by_user_id current_user.id
And I have to repeat this for every single action of every single controller. Is there perhaps a more succinct and DRY way of achieving the same effect? The amount of boilerplate I have to write feels wrong.
Thanks!
Take a look at the CanCan gem.
a) You can have a before callback in application_controller.rb that looks something like
def find_stuff_from_current_user
#stuff = Stuff.find_all_by_user_id current_user.id
end
And than call this in every controller like this:
before_filter :find_stuff_from_current_user
Now you have #stuff variable available in every controller and in every action.
b) Or you can use scoping in stuff model.rb where you say something like:
scope :stuff_from_current_user, where(:user => current_user)
I'm a Rails newbie.... Here's what I'm trying to do....
I created a scaffold for notes (t.text :content, t.integer :user_id)
What I want to do now is only allow user's to view notes that they created. ie (== user_id)
In my /app/controllers/notes_controller.rb
I have the following:
class NotesController < ApplicationController
before_filter :authenticate
before_filter :correct_user
.
.
.
.
def correct_user
#noteuserid = Note.find(:conditions=>["note.user_id=?", #noteuserid])
redirect_to(root_path) unless current_user?(#noteuserid)
end
I'm having problems understanding how to write the following line: #noteuserid = Note.find(:conditions=>["note.user_id=?", #noteuserid])
Any ideas?
Thanks
In Rails 3:
Note.where(:user_id=>current_user)
Or, you can start with the user...
User.find(current_user_id).notes.find(note_id)
So, firstly you want to find the Note being accessed by the user, then check whether that Note is valid for the user. I would try something like this (assuming that your current_user? method checks whether a given user id matches the current logged in user:
def correct_user
current_note = Note.find(params[:id])
redirect_to(root_path) unless current_user?(current_note.user_id)
end
Also, you may want to watch out for filtering all actions in the controller with your correct_user filter as actions to create a note may not have an id of a note to check against. Additionally, when you are viewing a collection of notes you will need to filter differently (e.g. Note.find(:all, :conditions => { :user_id => current_user_id })). It may be more appropriate to apply the correct logic in specific actions rather than as a generic filter.
Finally, you could look at the cancan plugin which would do a lot of the hard work for you with code like this.
I have a standard query that gets the current user object:
#user = User.find_by_email(session[:email])
but I'm putting it as the first line in every single controller action which is obviously not the best way to do this. What is the best way to refactor this?
Do I put this as a method in the Application controller (and if so, can you just show me a quick example)?
Do I put the entire #user object into the session (has about 50 columns and some sensitive ones like is_admin)?
Or is there another way to remove this kind of redundancy?
I suggest making it into a helper placed in the ApplicationHelper module
def current_user
return nil if #user === false
#This ensures that the find method is only called once
#user = #user || User.find_by_email(session[:email]) || false
end
I prefer the above usage instead of the standard #user ||= User.find... because it prevents repetitive queries if the user record isn't found the first time. You could also just bang the find method: find_by_email! to make it throw an exception when the user can't be found
You could specify a before_filter, which is automatically called at the beginning of every controller action. Read up on it to see how to use it.