My book says to run "rails g controller sessions" and edit it as
class SessionController < ApplicationController
def create
user = User.find_or_create_from_auth_hash(request.env['omniauth.auth'])
session[:user_id] = user.id
redirect_to root_path
end
end
What is session[:user_id]? If X is controller, Y and Z are some string, can I define as X[:Y] = Z?
The session[:user_id] is a special variable, that can be accessed like a hash,, storing all information you store into your application's session.
You can read more about the session in Rails here: http://guides.rubyonrails.org/action_controller_overview.html#session
It's basically a Hash that's shared between requests to store information and re-use it in sub-sequent requests.
session[:user_id] is a variable, that you earlier need to store and then reuse it throughout your session.
F.e. You have authentication and then you have to jump through pages, that needs user_id. So you can use session, to store it there. You can find more info there http://guides.rubyonrails.org/security.html.
No. session is just an object which respond to [](key) method. You cannot define something like controller[:foo] = 'bar'
Related
I'm trying to calculate a conversion rate. However I've only been able to get it working using
User.first
however I don't really want the first user, as my app can have many. I want the specific user that is currently signed in to be that user.
Here's how the conversion rate method looks (campaign belongs to user):
campaign.rb
def conversion_rate
fav = self.favorites.where(favorited: true)
val = fav.present? ? self.favorites.where(:owner_id => User.first.followers.map(&:follower_id)).count.to_f / fav.count.to_f * 100 : 0.0
val
end
what's the right method after the User here in order to find the actual user?
So I think you have current_user in your application_controller
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
then this will be accessible in all controllers and views
So you can call #campaign.conversion_rate(current_user), and pass current_user
def conversion_rate(user)
fav = self.favorites.where(favorited: true)
val = fav.present? ? self.favorites.where(:owner_id => user.followers.map(&:follower_id)).count.to_f / fav.count.to_f * 100 : 0.0 val
end
The conversion_rate method depends on a user. You can pass that into the method as an argument:
def conversion_rate(user)
fav = self.favorites.where(favorited: true)
val = fav.present? ? self.favorites.where(:owner_id => user.followers.map(&:follower_id)).count.to_f / fav.count.to_f * 100 : 0.0
val
end
Then it is only a matter of getting the currently logged in user. This depends on your application, but a typical Rails app has a current_user method defined in ApplicationController. You would have to call the conversion_rate method at a point in your code where current_user is available, i.e. in a controller action.
Basically, you want to get the user's ID from the current session. The details of this will vary based on how you've implemented user authentication and so on, but in general when a user logs in they'll get a session ID from your application that will be stored locally in their browser and used to track their actions while logged in. This ID is generally accessible through something like session[:user_id] in your application, enabling you to get the currently-logged-in user through something like User.find(session[:user_id]). See http://guides.rubyonrails.org/security.html#sessions for more information on this. Also, if you're using an authentication library (like Devise or something), check in its documentation for information on how it handles sessions.
I am new to ruby. I want to know when/where the Current user set. I know cookie will be generated for each URL request. And where the session details are stored? And where the current user set(in which file). Any one please explain briefly.
Hope you have a users table in your Rails application, so devise will automatically load all columns of users table in current_user.
It all depends on how you implement it. If you're using a library like Devise it has its own implementation, but usually such things are stored in encrypted Rails session store and on every request 'session' controller verifies visitor's cookie and only after that current_user is set to the User object from the session.
i prefer it in applicaton_controller..so that i can check where user_signed_in on every request and check the session ..if it exits then its ok else redirect_to login page..
for example in application_controller.rb
before_filter :check_current_user
def check_current_user
if current_user
#check if current user exists in our session
session[:current_user_id] = User.find(session[:current_user_id]).id
else
#if not ,then create new and set it to the session and return the current_user as u
session[:current_user_id] = User.create(:username => "guest", :email => "guest_# {Time.now.to_i}#{rand(100)}#example.com")
u.save!(:validate => false)
session[:current_user_id] = u.id
u
end
end
the above code is not perfect though..but i just wanted to show how current_user can be implemented to check current_user on every request using session and sets it in the session if there is no current_user as guest...
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.
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
Is there a way to limit the number of sessions in Ruby on Rails application (I'm using Authlogic for authentication)?
I would like to allow only 1 session per user account. When the same user is logging on another computer the previous session should be expired/invalidated.
I was thinking about storing the session data in database and then deleting it when a new session instance is created but probably there is an easier way? (configuration option)
I just ran into a possible solution, if you reset presistence token you can achieve the intended behaviour:
class UserSession < Authlogic::Session::Base
before_create :reset_persistence_token
def reset_persistence_token
record.reset_persistence_token
end
end
By doing this, old sessions for a user logging in are invalidated.
Earlier I implemented it as you mentioned: add a session_key field to the users table and make sure that the current session_id is stored for the user on login:
class UserSession < Authlogic::Session::Base
after_save :set_session_key
def set_session_key
record.session_key = controller.session.session_id
end
end
Then in the generic controller do something like this to kick out a user when someone else logged in with that same account:
before_filter :check_for_simultaneous_login
def check_for_simultaneous_login
# Prevent simultaneous logins
if #current_user && #current_user.session_key != session[:session_id]
flash[:notice] = t('simultaneous_logins_detected')
current_user_session.destroy
redirect_to login_url
end
end
i do exactly what your talking about, assign a session id to each uniq session, store that id in a cookie and the associated session data in a table. Works well. My aim wasnt to limit users to a single session, but rather keep the session variables server side to prevent user manipulation.