Rails 3, how to secure and protect controllers and urls - url

I'm sort of new to rails, what I want to to is protect users profile
what I mean is if user 1 login and go to edit his profile he can, but also if he change on the url to user to # 2 they can also change their information
localhost:3000/users/2/edit
I'm a little lost, any help will be greatly appreciated, or suggestions of good books/blogs

As part of authentication add a session variable, session[:user_id] = User.Authenticate(params[:user][:username], params[:user][:password) (this is the general pattern, you need to add another resource for authentication).
Then add a before_filter function to your controller and check if session[:user_id] == params[:id]. Look at it here: before_filter

The Rails Security Guide is probably a good place to start

Just in case this is useful to someone, something that I came across when testing my app was although users that hadn't signed in couldn't access restricted content, once a user was signed in, they could change the url to a another users id, eg.
/users/3 and it would then show that users home page. So any user could look at any other user, which wasn't what I wanted.
To get around this, I changed the user controller to secure against this:
class UsersController < ApplicationController
#first call the correct_user function before allowing the show action to proceed
before_filter :correct_user, only: [:show]
...
def show
#do whatever here
end
...
private
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user)
end

Related

Private profile for users | rails 4 | devise

I'm trying to implement a private profile page for each user. It's an learning app so people have access to the courses they paid for on their profile page. So other users can't access profile pages by entering URLs... The http response should be 404.
So far the idea was to create a profiles_controller
def show
#user = User.find(current_user)
End
So that each user can only access their own page.
Is there a best way of doing that?
Think of an ideal solution. The one where the problem doesn't even exist. Given the problem
"the user should not see other users' profiles"
we can build upon a principle
"there is only one profile for any user"
And we should reflect that mapping in our routes:
resource :profile # < not `resources`!
...and the profile will be available on /profile. No ids in your routes whatsoever, nothing to alter, therefore nothing to check. And it makes sense, why check anything, if we'll be working with the same resource anyway?
# ProfilesController
def show
#user = current_user
end
You can before_action for show method in ProfilesController. Basically, you need to check that the profile a user can view - belongs only to that user.
I assume the URL is /users/:user_id/profiles/:id
before_filter :check_profile, only: :show
def check_profile
user = User.find_by_id params[:user_id]
unless params[:id] == user.profile.id
redirect_to users_profiles_path(user, user.profile)
end
end
This way, user will be redirected to his own profile path, whenever he'd try to access someone else's profile.

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'.

Ruby on Rails app planning - login system routing when not logged in

I'm building a rails app, and so far I've set up a pretty basic user registration/login system, mainly by following this railcast I found thanks to stack overflow. I've left everything the same as the railcast, only used strong parameters instead of attr_accessible and added some additional fields (username, bio, img url) to the table.
Now I want my app to redirect users onto my login page if they're not logged in, no matter what page they try to access, and if they are, then redirect to the normal path. My loging page is currently my root_path. Do I need to do this in all the controllers separately or can I just write this into my appController? How would I go about writing the controller? I was thinking something like this:
if session[:user_id] == nil
redirect_to login_path
else
redirect_to current_controller_path
end
Now how do I check if user is logged in, and how do I redirect to current controller path (for instance articles_index_path?
I am new to ruby on rails, and still trying to wrap my head around models, views and controllers, so please assume I know nothing when writing up explanations :) Thanks for the help
Oh I'm using Rails 4 with ruby 2.2.1
You need to add a before_filter in your ApplicationController to check user's authentication.
class ApplicationController < ActionController::Base
...
before_filter :authenticate_user!
...
private
def authenticate_user!
redirect_to login_path unless session[:user_id]
end
end
Now it will make sure that user should be logged in for accessing any action of any controller, including signup, signin and other actions which should be accessible to non-logged in users too.
You need to make sure that you skip above before_filter where you don't want user to be logged in such as signup, signin, about us, contact us etc actions like this.
For Example:
class SessionsController < ApplicationController
skip_before_filter :authenticate_user!, :except => :destroy
...
def new
...
end
def create
...
end
...
end
You can read more about skip_before_filter on APIDock

RESTful account selection controller in Rails?

In my app, after a user logs in, he is redirected to the AccountsSelection controller. In there, I have an index action that basically will get all the potential accounts a user can use, and display them in a index view.
def index
#accounts = current_user.eligible_accounts
end
In that page the user can click in one of the accounts, and that should go to one of the actions in the controller, that will set this in the session:
def show
session[:selected_account] = params[:account_id]
redirect_to account_path
end
Right now I have made that action to be show (so the selection of the account is doing a GET request, but I am not sure if this is a RESTful way of approaching this). From my point of view this is not a PUT/POST because I am not changing any resource or creating any resource, but it seems weird to be using the show action to just set a session.
What would be a proper way of handling this? It is correct to use show?
It is not recommended to use session in this case. So the standard approach is to create a before_action to set the account_id.
Use like this:
before_action :set_account, only: [:show]
and create the function in private as:
private
def set_account
account_id = params[:account_id]
end
Hope this helps.

How to allow only Admin (or some user) to edit the pages in Rails?

I have a scaffold Finances and I just realized that it can be edited by any logged in user by going to /finances/1/edit
I have installed activ_admin gem but I don't think it is what I need. How to make sure other than admin (or may be some users) no one can edit finances resource type- I
EDIT - I found https://github.com/EppO/rolify, is this best option or I still can do something better as it may be overkill ?
EDIT 1 - I went through this https://github.com/EppO/rolify/wiki/Tutorial and have assigned role "admin" to user = User.find(1), everything went well upto "ability.can? :manage, :all" in console, which shows TRUE for user 1 and false for other users. Now I am not able to figure out what to do ? I can still see all users being able to edit the page even though I have added "resourcify" in the finance.rb model. Any help ?
Well, I personally use rolify for my project and love it.. but to be honest this is super easy to achieve by simply adding a column "admin" to your User model and having it default to false. When you want a user to be an admin update the attribute to true and then require the User.admin==true to access the finances edit action... You can do this by redirecting the non-admin user from the controller (within the finances edit action)
By the way if you're using devise for auth check out Devise before_filter authenticate_admin?
I'm not sure how your models are set up, but lets say your User model has an admin column, you can do the following:
FinancesController < ApplicationController
before_filter :must_be_admin, only: :edit
def edit
...
end
private
def must_be_admin
unless current_user && current_user.admin?
redirect_to root_path, notice: "Some message"
end
end
end
You can add any actions needed to the before filter, e.g. before_filter :must_be_admin, only: [:edit, :destroy]
If you're looking to add sensible user authorization without rolling your own solution, definitely check out CanCan. Also helpful is this screencast by its author, Ryan Bates.

Resources