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.
Related
I have a user_id column. Instead of calling for all members how can I call up members based on current user's and user_id?
This is my controller, I tried changing .all to user_id or current_user.id plus many variations based on examples. Still can't get it. I also have no models (using authrocket). The create action also works and inserts the user_id, I have a def current_user at the bottom.
class Members::MainsController < ApplicationController
# Member Profile List
def index
#members_mains.user_id = current_user.id
#members_mains = Members::Main.all
end
private
# Common Callbacks
def set_members_main
#members_main = Members::Main.find(params[:id])
end
# White List
def members_main_params
params.require(:members_main).permit(:mfirstname, :mlastname, :mtitle, :memail, :mphone, :mnotes, :smfacebook, :smtwitter, :smlinkedin, :user_id)
end
end
If I got it right, your index action should be something like this:
# Member Profile List
def index
#current_member = Members::Main.find(current_user.id)
end
Do you intend to show a list of profiles for all members?
If not, your index action can simply be removed. If so, you wouldn't normally filter on a user_id at all for that action and you can remove that line.
To load a member profile for a specific user_id, try a show action something like this:
def show
#members_main = Members::Main.find_by(user_id: params[:id])
end
That will load a member profile based on the :id from the URL.
If you want to just show the current user's own profile instead, use current_user.id which will give you the AuthRocket user's ID.
def show
#members_main = Members::Main.find_by(user_id: current_user.id)
end
In either case, you may need to remove :show from the set_members_main callback.
Lastly, you probably want to remove :user_id from members_main_params so that users can't modify the AuthRocket user_id. You only want to control that directly (as you already are in the create action).
Hopefully that's clear enough to get you on your way, but I could be off a bit based on what functionality you actually intend for index and show.
I'm writing a ruby on rails website for the first time. I have a User model and a Manager model. The user has_one Manager and a Manager belongs_to a User. The Manager model contains more info and flags regarding privileges. I want to allow an admin while viewing a User (show) to be able to make him a manager.
This is what I wrote (probably wrong):
In the view: <%= link_to 'Make Manager', new_manager_path(:id => #user.id) %>
In the controller:
def new
#user = User.find(params[:id])
#manager = #user.build_manager
end
resulting in a managers/new?id=X Url.
I would separate roles and permissions from the User class. Here's why:
Managers are users too. They share the same characteristics of Users: Email address, first name, last name, password, etc...
What if a manager also has a higher level manager? You'll have create a ManagerManager class, and that's terrible. You might end up with a ManagerManagerManager.
You could use inheritance, but that would still be wrong. Managers are users except for their title and permissions, so extract these domains into their own classes. Then use an authorisation library to isolate permissions.
You can use Pundit or CanCan. I prefer Pundit because it's better maintained, and separates permissions into their own classes.
Once you have done that, allowing a manager to change a normal user to a manager becomes trivial and easy to test:
class UserPolicy
attr_reader :user, :other_user
def initialize(user, other_user)
#user = user
#other_user = other_user
end
def make_manager?
user.manager?
end
end
In your user class you can have something like:
def manager?
title == 'manager?'
# or
# roles.include?('manager')
# Or whatever way you choose to implement this
end
Now you can always rely on this policy, wherever you are in the application, to make a decision whether the current user can change another user's role. So, in your view, you can do something like this:
- if policy(#user).make_manager?
= link_to "Make Manager", make_manager_path(#user)
Then, in the controller you would fetch the current user, and the user being acted upon, use the same policy to otherwise the action, and run the necessary updates. Something like:
def make_manager
user = User.find(params[:id])
authorize #user, :make_manager?
user.update(role: 'manager')
# or better, extract the method to the user class
# user.make_manager!
end
So you can now see the advantage of taking this approach.
I'm on a dilema, a have a ton of objects associated to the current_user on my app. And and don't know if in my controllers i keep using the IDs to find these objects or put directly the current_user + object.
Exemple:
class HousesController < ApplicationController
def show
#house = House.find(params[:id]) **or?** #house = current_user.house
end
def edit
#house = House.find(params[:id]) **or?** #house = current_user.house
end
end
And this going on and on. thank's in advance
If you use House.find(params[:id]) you have a potential security hole, as a given user could simply change the number in the url and access the house for a different user. So if you go this route, you have to add something to protect unathorized access.
OTOH, current_user.house keeps them on their own house, but needs alternate code for admin functions.
For simple applications, you can do this by hand, but for larger applications, you might want to consider authorization frameworks such as cancan or declarative_authorization where you can more easily define the permissions.
I use decl_auth myself, and all my controllers either use its method of loading the resource with filter_resource_access (loads the appropriate resource or throws and error if not allowed) or by hand with House.with_permissions_to(:index) which will only give me a house if I have permission to load it.
As always, Railscasts say it best: cancan and declarative authorization.
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.
My Rails application have a User model and a Group model, where User belongs to a Group. Thanks to this, a user can be a admin, a manager, a subscriber, etc.
Until recently, when for example a new admin need to be create on the app, the process is just to create a new normal account, and then an admin sets the new normal account's group_id attribute as the group id of the admin... using some condition in my User controller. But it's not very clean, I think. Because for security, I need to add this kind of code in (for example) User#update:
class UsersController < ApplicationController
# ...
def update
#user = User.find(params[:id])
# I need to add some lines here, just as on the bottom of the post.
# I think it's ugly... in my controller. But I can not put this
# control in the model, because of current_user is not accessible
# into User model, I think.
if #user.update_attributes(params[:user])
flash[:notice] = "yea"
redirect_to root_path
else
render :action => 'edit'
end
end
# ...
end
Is there a clean way to do it, with a Rails plugin? Or without...
By more clean, I think it could be better if those lines from User#update:
if current_user.try(:group).try(:level).to_i > #user.try(:group).try(:level).to_i
if Group.exists?(params[:user][:group_id].to_i)
if Group.find(params[:user][:group_id].to_i).level < current_user.group.level
#user.group.id = params[:user][:group_id]
end
end
end
...was removed from the controller and the application was able to set the group only if a the current user's group's level is better then the edited user. But maybe I'm wrong, maybe my code is yet perfect :)
Note: in my User model, there is this code:
class User < ActiveRecord::Base
belongs_to :group
attr_readonly :group_id
before_create :first_user
private
def first_user
self.group_id = Group.all.max {|a,b| a.level <=> b.level }.id unless User.exists?
end
end
Do you think it's a good way? Or do you process differently?
Thank you.
i prefer the controller methods to be lean and small, and to put actual model logic inside your model (where it belongs).
In your controller i would write something along the lines of
def update
#user = User.find(params[:id]
if #user.can_be_updated_by? current_user
#user.set_group params[:user][:group_id], current_user.group.level
end
# remove group_id from hash
params[:user].remove_key(:group_id)
if #user.update_attributes(params[:user])
... as before
end
and in your model you would have
def can_be_updated_by? (other_user)
other_user.try(:group).try(:level).to_i > self.try(:group).try(:level).to_i
end
def set_group(group_id, allowed_level)
group = Group.find(group_id.to_i)
self.group = group if group.present? && group.level < allowed_level
end
Does that help?
Well if you have a User/Groups (or User/Roles) model there is no other way to go than that you have underlined.
If it is a one-to-many association you can choose to store the user group as a string and if it is a many-to-many association you can go for a bitmask but nonetheless either through business logic or admin choice you need to set the User/Group relation.
You can have several choices on how to set this relationship in a view.
To expand your model's capability I advice you to use CanCan, a very good authorization gem which makes it super easy to allow fine grain access to each resource in your rails app.