Rails - Securely updating user information - ruby-on-rails

In many books and tutorials, I see people implementing the update method in users controller as either find the user data by params[:id] or session[:id]. However, I feel it possible to simply modify the session or the address bar to hack into someone else's account and modify his/her information. So how to I make sure the user can only update his/her own information instead of changing others.

The answer is application specific. You'll need to first "find" the record - where the details of how to find that record depends on the application.
Yes, as you indicate, you should not just blindly instantiate the record and update it. Your business logic needs to intelligently find the pertinent record, before you expose it to be updated.

Treat the User model the same as any other model and add your authentication to it and require the user to be logged in (e.g. current_user for some auth methods) within the uddate method.
if current_user
update_stuff and redirect
else
:notice => 'Not authd' and redirect
end

I once had this problem when working on applications that required some things to be private. What I did was use Devise and Cancan along with great rails practices. I used Devise for authentication, cancan for authorization.
In some case you can pass the user id through your form using a hidden_field. I recommend passing the user_id in your controller create action and also when using devise you have access to the current_user method which would only create the model based on the current users session and id.
Secure the users information using cancan. For example if I could only show a users photos like so without user2 seeing the photos.
#photos = current_user.photos #only grabs current users photos
With cancan I could stop the user from manually entering a url like users/2/edit by doing somthing like this
Ability.rb
can :manage, Photo, :user_id => user.id
controller.rb
load_and_authorize_resource :photo
def index
authorize! :index, #photo
end

I have actually done quite a bit of application building/research around this topic so I will try to give a detailed answer.
In the same kind of way you can divide sections of an application or website between non-users, signed-in users and admin accounts, you can implement checks to ensure that, for example, only the correct user can change their account password. Here is a test you might use for such a method:
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
end
If you are new to testing (or unsure what this specifically does) I will explain:
In the code a factory can take an option (this particular line below)
FactoryGirl.create(:user, email: "wrong#example.com")
This basically creates a user with a different email address from the default. Then all that is happening is the tests specify that the wrong user should not have access to the original user’s edit or update actions.
The next step would be to add a before filter to check the status of the current user. This might go in your users_controller.rb file.
before_action :correct_user, only: [:edit, :update]
Then secondly (still in the users_controller.tb file) create a private method to check the current user has permission to modify the data otherwise redirect them back to the root url (if you were fancy you could use a [:notice] saying "you do not have access to this part of the applications" or something similar)
private
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
The correct_user filter uses the current_user? boolean method which you define in the Sessions helper as follows:
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
That isn't exactly the entire dogma for security when it comes to who can access what but I hope it provides you with the missing details mentioned in your question.

Related

Rails Pundit Policy Spec test failing with NoMethodError

I have just started using Pundit for authorization in my current project along with the pundit-matchers gem.
So far it seems to generally be working for me but I have a problem in my tests.
I have generally tried to follow the examples in the pundit-matchers readme and the Thunderbolt labs blog (http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/).
This is my policy file;
#app/policies/procedure_policy.rb
class ProcedurePolicy
attr_reader :user, :procedure
def initialize(user, procedure)
#user = user
#procedure = procedure
end
def index?
user.admin?
end
end
And this is my policy_spec file
require 'rails_helper'
describe ProcedurePolicy do
subject {described_class.new(user, procedure)}
let(:procedure) {FactoryGirl.create(:procedure)}
context "for a guest" do
let(:user) {nil}
it {is_expected.not_to permit_action(:index)}
end
context "for a non-admin user" do
let(:user) {FactoryGirl.create(:user)}
it {is_expected.not_to permit_action(:index)}
end
context "for an admin user" do
let(:user) {FactoryGirl.create(:admin_user)}
it {is_expected.to permit_action(:index)}
end
end
2 of my 3 tests pass; The "for a non-admin user" and "for an admin user" ones. The "for a guest" test fails with
NoMethodError:
undefined method `admin?' for nil:NilClass
Now I understand why. I'm passing nil to the #index? method of my ProcedurePolicy class which will not have an #admin? method. But all of the example specs I have found online do exactly this. What am I not seeing.
Apologies if I'm missing something really obvious. I've been away from coding for a couple of years.
Pundit calls the current_user method to set user, and typically that is provided by an authentication system like Devise or a custom solution. This means that in most scenarios, the expectation is that you always have a layer of authentication before you hit the Pundit logic, so you never have a user set to nil when it gets there.
If you want Pundit authorizations to work directly without authentication, you have to handle that in your policy definitions, ex:
class ProcedurePolicy
def index?
user.present? && user.admin?
end
end
http://www.rubydoc.info/gems/pundit#Policies
The "being a visitor" context is for testing authorisation attempts when there is no user currently authenticated in the system. Since no user is logged in, no user record/object exists - the absence of a user object is nil. This is why both the Thunderbolt Labs and pundit-matchers examples use nil to represent a visitor. The nil object does not have an admin? method, causing the error.
To correctly handle nil/guest users in your policy, check that the user object is present before checking if the user is an admin, either by checking the user object directly or using the present? method in Rails, e.g.
def index?
user.present? && user.admin?
end
or just:
def index?
user && user.admin?
end
You'll find that authentication systems such as Devise return nil from the current_user method when no user is signed in.

RESTful routing best practice when referencing current_user from route?

I have typical RESTful routes for a user:
/user/:id
/user/:id/edit
/user/:id/newsfeed
However the /user/:id/edit route can only be accessed when the id equals the current_user's id. As I only want the current_user to have access to edit its profile. I don't want other users able to edit profiles that don't belong to them.
What is typically the best practice to handle this situation?
Should I leave the route as is, and thrw an error if the current_user.id != param[:id], forcing the front end client calling the api to track the logged in user's id?
Should I make a special route /user/self/edit and in the controller check to see if param[:id] == 'self'?
I would've added special routes for current user profile actions, in this case you don't have to check anything. Just load and display the data of current user. For example:
/my-profile/edit
/my-profile/newsfeed
It's not that RESTful but you don't have to put extra checks keeping your code clean.
If you still have to have (or want to have) a strict RESTful routes then I would use a before_filter and check if the id = current_user.id. If not then return 401 or 403.
I only want the current_user to have access to edit its profile. I
don't want other users able to edit profiles that don't belong to
them.
What I suggest is to use some authorization gems like pundit
Sample code:
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
def edit?
#current_user == #user
end
end
Also with an authentication gem like Devise, only the current_user(the users who logged in) can only access and edit their profiles
I would say that you are doing it correctly, just keep your current route as it is right now. And what you should do is to add a restriction in your controller instead. I would assume that you are using Rails, and working on users_controller.
class UsersController < ApplicationController::Base
def edit
if current_user.id == params[:id]
# do your work
else
render :404
end
end
end
Or you could clean up your controller by moving the restriction into a callback instead:
class UsersController < ApplicationController::Base
before_filter :restrict_user, only: [:edit]
def edit
# do your work
end
private
def restrict_user
render :404 unless current_user.id == params[:id]
end
end
You can add the gem "cancancan" and after the initialize....
class Ability
include CanCan::Ability
def initialize(user)
can :update, User do |user|
user.id == params[:id]
end
end
end
Then add this authorize! :edit, #user to your update action
You're going to need to add authorization code in all the user_controller methods as another comment suggested. Usually what I do in apps where a user is only supposed to edit their own profile I add a /profile route for a user to edit their own profile and then on the main /users/:id/* routes I add logic to prevent non-admin users from accessing those routes.
User is able to view his profile /users/1 or edit his profile /users/1/edit. From users perspective this URLs are absolutely fine.
There is no links which may lead user to edit the another user. You are trying to cover the different situation: when someone manually trying to craft the URL and get access to another account. I would not call them hackers, but technically they are – users who are trying to exploit your website to pass the restrictions.
You don't have to worry about "hackers" convenience. I'm always use current_user in edit action so nobody can edit wrong profile whatever his profile is.
def edit
#user = current_user
end
Also, I need to mention that you should also cover update action with such checks. With edit you may only get data (and probably only wide-public open data, unless you put billing information or plain-text-passwords inside your edit template). But with update you can actually change the data, which may be more destructive.
Because it seems that the only available user resource should be the authenticated user, I think the best way to solve this is
GET /user
PUT /user
GET /user/newsfeed
If you like to extend the api usage in future so that one user could have access to other user resources, than you need a solution that includes the user ids. Here it makes sense to introduce the routes for "self", too. But then you also have to implement an access check on server side.
GET /user/id/:id
PUT /user/id/:id
GET /user/id/:id/newsfeed
GET /user/self
PUT /user/self
GET /user/self/newsfeed
But I think you should keep it as simple as possible
For further investigations I would propose books like http://apigee.com/about/resources/ebooks/web-api-design which give a good introduction into API design
Since you only care to provide RESTful endpoints only for the currently authenticated user, which is available in your controllers as current_user, i say you don't need the id identifier parameter. I suggest using the following routes:
GET /user => users#show
PUT/PATCH /user => users#update
GET /user/edit => users#edit
You should keep the url as it is. Authentication and Authorization are separate concerns. 'current_user' refers to the user who is authenticated to access the apis. The id in the url identifies the resource on which 'current_user' is working, so does he have access to that resource or not is the concern of authorization. So you should add current_user.id != param[:id] (as you mentioned) in your api permissions and throw 403 status code in response.
You should use this route:
PUT /user/me
Note that there is no need for "edit": you should use the PUT method instead.
Also, you should explicitly define the route I've written above, instead of checking if id == 'self'.

How to secure user show page alongside user admin functions when using devise

I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.

Setting CanCan ability.rs model

I successfully made login system with Devise and CanCan, and I have 3 types of users. Admin, internal and global users. I created Controllers and index actions: Admin, Cpanel, Report and State, and I want to restrict access to this controllers for some users.
Admin user should have privilegies to access: Reports(all), State (read), Admin (all)
Global user should have privilegies to access: Reports(only read), State(read), Cpanel(all)
Internal user should have privilegies to access: Reports(all), State (read)
And I tried to do this with following code in ability.rs:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :admin
can :manage, [Report, Admin]
can :read, State
elsif user.role? :global_user
can :read, [Report, State]
can :manage, Cpanel
elsif user.role? :internal_user
can :manage, Report
can :read, State
end
end
end
At this time I have only index actions in this controllers, and when I login to app with internal user I CAN access to /admin for example, and that is not behavior that I want. I want to restrict access to all controllers instead of controllers listed in ability.rb class.
Source code is here
If I were going to prevent access to an entire controller, I would make a before filter that redirects the user to an access denied page if he does not have the admin role. Might look something like:
def check_permissions
raise CanCan::AccessDenied unless #current_user.role?(:admin)
end
If I just wanted to prevent access to update and create, for example, I would do:
def update
raise CanCan::AccessDenied unless can?(:update,Thing)
...
end
def create
raise CanCan::AccessDenied unless can?(:create,Thing)
...
end
You can handle the CanCan::AccessDenied exception in your application controller:
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = exception.message
redirect_to no_access_path
end
I have some pretty good posts about CanCan and Devise here and here
UPDATE
I use this method in my application controller to set my current user variable:
# Make the current user object available to views
#----------------------------------------------------------------------------
def get_user
#current_user = session[:current_user]
end
You need to add checks for the cancan authorization to your controllers.
This might be just adding a line like
authorize! :read, #state
to your state controller index action, and similarly for all the other index actions.
EDIT:
Sorry, in a state controller index action, you likely don't have #state, so the above wouldn't apply. Possibly something like
authorize! :read, State
There is also a load_and_authorize method that you can use to combine authorization for multiple actions in a controller and reduce your code. The load_and_authorize version is likely to look similar to
load_and_authorize_resource :state
and it should be before your actions.
You might want to look at this railscast on cancan authorization for a complete basic setup (in rails2).
I suspect to clear up other problems, we might need to see some more code. Try posting some of your controller code.
I haven't used this in rails3, but I assume most of it remains more or less similar.
I solved this problem with
def check_permissions
raise CanCan::AccessDenied unless current_user.role?(:admin)
end
but, note that I must to change #current_user to current_user (without #)
Thanks Tony

Force Restful Authentication to login as a specific user (an admin function)?

I'm using Restful Authentication and I'd like to be able to log in as different users on our site to investigate issues they may be having ("see what they see"). Since all passwords are encrypted I obviously can't just use their passwords.
So, how can I force a session to be logged in as a specific user?
In your sessions_controller add action impersonate like this:
def impersonate
user = User.find(params[:id])
logout_killing_session!
self.current_user = user
flash[:notice] = t(:logged_in)
redirect_to root_url
end
Then in your routes extend session resource with the member impersonate:
map.resource :session, :member => {:impersonate => :post}
Finally, somewhere in your admin views add a button to each user called "Impersonate". It will have to look something like this (assuming that user is in local variable user):
<%= button_to "Impersonate", impersonate_session_path(:id => user.id) %>
Using this approach you also avoid overriding any tracking data such as time of the last login, etc.
P.S. Don't forget to require admin for impersonate action in sessions controller.
Simply override session[:user_id] with the id of the user you want to be. One easy way is to have the user log in as an admin and then give them a drop-down of usernames. When they submit the form, have the controller set session[:user_id] and then reload current_user. The admin will then 'become' that user.

Resources