I'm still kind of new to Rails and I'm not understanding why I cant access #user in a custom view template. But it works perfectly fine when it's being called in one of the standard actions like show, edit, update, destroy
for example I created this route
get 'mygroups' => 'users#mygroups'
In the users_controller I added
def mygroups
#user = User.find(params[:id])
end
then I created a new view template called mygroups.html.erb under Users and added this to the view
<%= #user.first_name %>
I get an error saying
Couldn't find User with 'id'=
I have a current_user object though that I can use in the view without any problem.
<% current_user.first_name %>
I just don't understand why I can't load the #user object in my mygroups method to get it to show in the mygroups.html.erb template. Are there any drawbacks to using current_user instead of #user ? how come the users controller has show, update, etc... loading #user by calling #user = User.find(params[:id]) but it doesn't in my method?
I'm guessing when you're accessing the mygroups route, you're not passing in an id, hence the error message saying it's blank. The default CRUD user actions (show, update etc) will all have routes setup to expect the id, and any links to those actions (or form URLs etc) will carry through the user id in question.
The difference between #user and current_user in this sense, is that current_user is generally your authenticated user, based on the current session. However, #user in the users controller is the user being acted upon. Imagine that you have an app where your current_user is some kind of admin, and that admin is allowed to create, read, update and destroy other users. The #user being acted upon is a specific user, rather than the currently authenticated one.
Try accessing your mygroups route with an id, /mygroups?id=USER_ID if you're sure you want it to work for any user - if you only want it to work for the current user, lose the User.find lookup and just use current_user instead.
Hope that helps!
current_user is almost always (99% of the time) the current logged in user. This method is defined (behind the scenes in your case) in ActionController.
Your error message is indicative of not having an id param present, whether by POSTed parameters or parameters in your query string.
Since you're new to Rails, you may find the classic Rails Tutorial a great and beneficial read. https://www.railstutorial.org/
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'.
In one of my controllers I have this method:
def method_name
if current_user
#model = Model.find(params[:id])
if #model.destroy
flash.alert = 'Model deleted successfully'
redirect_to models_path
end
end
end
I check if there is a current_user assigned by devise before giving the ability for the #model to be deleted. Is this safe and sufficient in terms of security?
What I really do is just checking if current_user exists. So is there a way that somebody can "trick" the system that current_user does exist and as a result be able to trigger the commands included in the method?
You will get a spectrum of answers in this. But if you want the user to be logged in then just do this at the top of your controller:
before_filter :authenticate_user!
That is provided by devise and ensures that there is a logged in user before allowing any controller actions.
If you have simple authorization then yes, most likely though you are going to want to make sure that the user has the authorization to delete the object. You can do that several ways. My favorite one right now is the Pundit gem.
You could also just check that the user owns the object in order to be able to delete it. That code would look something like this
#model = Model.find(params[:id)
if current_user.id == #model.user_id
# Rest of your destroy code
end
I'm building something simple on Rails and I have Devise installed. Users log in and create links - I have create one account, made a few links and then when I create another account I can go to /links/5 and still see the content that another user has posted.
I'm looking to make this App as personal as possible, any ideas on how to solve this?
One option is to use a simple before_filter in the LinksController. Something like:
def check_authorization
if current_user != link.user
redirect_to ... (Redirect as appropriate for your app)
end
end
This assumes there's a link method that will get the current link. Something like
def link
#link ||= Link.find(params[:id])
end
As an earlier commenter mentioned, Cancan is also a viable option.
I've got Devise working in my Rails app but I can't figure out how to now lock it down so a user can only edit their own record in the users table AND in tables belonging to the user.
What am I missing here? Is this done in the controller or the model?
I would create a helper in application_controller for current_user and remove the use of User.find
The simplest way of creating an authorization is with a boolean flag (admin true/false). For other simple solutions are cancan, as mentioned by Yannis or easy_roles KISS is recommend to start with. You may implement the edit action like this
def edit
if current_user.is_admin?
User.find(params[:id])
else
current_user
end
end
application_controller.rb
def current_user
UserSession.find
end
To limit access by the user, like a user having his/hers own tasks, do this.
def index
#tasks = current_user.tasks
end
Devise is an authentication solution. You now need an authorization system. Have a look to Ryan Bates CanCan (github, railscasts).