So lets say I have a form for submitting a new post.
The form has a hidden field which specify's the category_id. We are also on the show view for that very category.
What I'm worried about, is that someone using something like firebug, might just edit the category id in the code, and then submit the form - creating a post for a different category.
Obviously my form is more complicated and a different scenario - but the idea is the same. I also cannot define the category in the post's create controller, as the category will be different on each show view...
Any solutions?
EDIT:
Here is a better question - is it possible to grab the Category id in the create controller for the post, if its not in a hidden field?
Does your site have the concept of permissions / access control lists on the categories themselves? If the user would have access to the other category, then I'd say there's no worry here since there's nothing stopping them from going to that other category and doing the same.
If your categories are restricted in some manner, then I'd suggest nesting your Post under a category (nested resource routes) and do a before_filter to ensure you're granted access to the appropriate category.
config/routes.rb
resources :categories do
resources :posts
end
app/controllers/posts_controller
before_filter :ensure_category_access
def create
#post = #category.posts.new(params[:post])
...
end
private
def ensure_category_access
#category = Category.find(params[:category_id])
# do whatever you need to do. if you don't have to validate access, then I'm not sure I'd worry about this.
# If the user wants to change their category in their post instead of
# going to the other category and posting there, I don't think I see a concern?
end
URL would look like
GET
/categories/1/posts/new
POST
/categories/1/posts
pst is right- never trust the user. Double-check the value sent via the view in your controller and, if it does't match something valid, kick the user out (auto-logout) and send the admin an email. You may also want to lock the user's account if it keeps happening.
Never, ever trust the user, of course ;-)
Now, that being said, it is possible to with a very high degree of confidence rely on hidden fields for temporal storage/staging (although this can generally also be handled entirely on the server with the session as well): ASP.NET follows this model and it has proven to be very secure against tampering if used correctly -- so what's the secret?
Hash validation aka MAC (Message Authentication Code). The ASP.NET MAC and usage is discussed briefly this article. In short the MAC is a hash of the form data (built using a server -- and perhaps session -- secret key) which is embedded in the form as a hidden field. When the form submission occurs this MAC is re-calculated from the data and then compared with the original MAC. Because the secrets are known only to the server it is not (realistically) possible for a client to generate a valid MAC from the data itself.
However, I do not use RoR or know what modules, if any, may implement security like this. I do hope that someone can provide more insight (in their own answer ;-) if such solutions exist, because it is a very powerful construct and easily allows safe per-form data association and validation.
Happy coding.
Related
I've a user profile (with name, logo, about_me) which is created after user creation(using Devise). Profile table uses user_id as Primary key.
Now I want that whenever the user creates/updates a post, while filling in form some details are taken from profile, so profile data or #profile be available in post form as I cannot expose my model in form.
To set post.myname attribute in create and #update I'm doing this:
#myprofile = Profile.find_by_user_id(current_user)
write_attribute(:myname, #myprofile.name)
I read from various sources but what's the best solution of the 4 given and if anyone can back with easy code as I do not want to do something extensive? Thanks in advance.
1)Form Hidden fields - Like get the profile data as above in hash in #edit and then pass through form and access fields in #update but that way we will pass each field separately. Can one #myprofile be passed?
2)Session - I feel if profile data is stored in a session and someone updates profile then updated data won't be available in that session.So not sure if it is plausible.
3)Caching - easy way to do that?
4)polymorphic profile---tried it but I didnot get relevant example. I was stuck with what to put as profileable id and type and how to use them in the code.
If your Profile and User models have a one-to-one relationship with each other, the simplest solution is to remove the Profile model altogether and move its fields into the User model.
Devise already queries the database to obtain the current_user object. So, your example would like this:
write_attribute(:myname, current_user.name)
Which wouldn't hit the database (after Devise has retrieved the current_user object).
If you're forced to keep the Profile model, in looking at your four scenarios ...
You could use a session variable. Something like:
session[:profile_name] ||= #myprofile.name
This would go in a controller action.
The trick here is that you will want to redefine the each relevant session variable if the profile gets updated. And because you don't have access to the session in the model, you'd be best to perform that action in the controller. So, not pretty, but it could work.
You could also use low-level caching, and save the profile relationship on the user. In general, you could have a method like this in your user model:
def profile_cached
Rails.cache.fetch(['Profile', profile.id]) do
profile
end
end
Here, too, you will have to know when to expire the cache. The benefit of this approach is that you can put this code in the model, which means you can hook its expiration in a callback.
Read more about this in Caching with Rails.
I would avoid hidden fields and I'm not sure how a polymorphic relationship would solve you not hitting the database. So, #2 and #3 are options, but if you can combine the two models into one, that should simplify it.
Working through authentication in RailsCasts, there was one where I didn't understand something completely fundamental and important (it's pro, sorry - you need to be subscribed to access it).
He creates a user model with an email and encrypted password (has_secure_password).
Then he makes a new controller called Sessions, and declares it as a resource in the config. i.e.
resources :sessions
Then, inside the sessions controller, he defines the create method like this:
user = find user and authenticate
if user
session[:user_id] = user.id
else ...
But sessions as a model doesn't exist. For instance, if I open up my console and write
sessions[:user_id] = "hello"
it throws. Does anyone have an explanation or a link to this very basic concept that I'm missing?
Thanks!
Edit: Thanks Sanfor. Typo fixed, also for markup plus most importantly an answer!
I suppose you have copied the session as sessions in your question, is that correct? The screen cast is revised, so you'd need to be subscribed what I'm not nowadays so can't confirm it more than what comments say.
Now to the actual answer, the session is Rails internal reference to the actual session on hand as described here and for that reason you can't see the model for it created.
Simplistic explanation:
session is just a hash and stored as a cookie. (Unless you specifically instructed Rails to store it in the database). Models are typically ActiveRecord based and have some behavior.
You can add to it by simply
session[:some_thing] = "Info for session"
session[:store_this_too] = "Some other info to track for this session"
The session hash is created by the controller-related class/modules and rails console doesn't load them. Therefore, it is not available in the console.
Here's a pretty old Railscasts which explains a bit more. And this which takes the model-based approach. Bear in mind they are from the old days.
I have a model Post, which is submitted and graded by different Users. The submitter and grader are identified by submitter_id and grader_id in Post model. Note that an user is both a submitter himself and a grader to others.
I want to make sure that the submitter can only edit the content of the Post but not the grade. Likewise, the grader can only edit the grade but not the content.
Is multiple edit methods the way to go? How should I accomplish this otherwise?
You can have a role column in your users table, and the role can be either submitter or grader. Not sure what you are using for authentication, but in case you are using devise, you can access the currently logged in user with current_user helper (in case you are using something else, figure this part out, or add a new helper).
Now in your update method, you can do something like this:
# Controller
# scope post to current user, so that a user cannot edit someone else's post. A crude way to achieve this is post = Post.find(params[:id])
post = current_user.posts.find(params[:id])
post.content = params[:content] if post.submitter?(current_user.id)
post.grade = params[:grade] if post.grader?(current_user.id)
post.save!
# Model - Post.rb
def submitter?(user_id)
self.submitter_id == user_id
end
def grader?(user_id)
self.grader_id == user_id
end
The advantage of keeping those methods in the model is that in case you permission logic changes (who is submitter, or a grader), you need to change it at a single location. DRY.
You can modify the above approach to show error messages, and do other similar stuff. In case you are looking for more granular authorization control, you can look into cancan gem:
https://github.com/ryanb/cancan
Your post model should only be concerned with persisting data. Better to use plain old ruby objects to encapsulate the higher order behavior of grading and submitting. Consider using service objects or form objects.
Each service or form object can then include ActiveModel::Model(rails > v4) to get its own validations.
See more about service and form objects here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
If you only have one submit action and one grade action, its probably ok to keep in one controller. But if you start having multiple actions that are related to submitted, and multiple actions that are related to grading, this sounds like they would make great resources controllers on their own.
I am new to RoR and started working on a typical 'has_many' association (ie. a user has many friends). I have everything working correctly, but I don't like having the ids exposed in the url. I find that I need to add extra validation in my controller to make sure the ids represent valid associations in case the user manually entered different ids.
Personally I would like to see the ids out of the url and passed via some other means but that is not always possible. Shallow nesting of resources will help reduce the number of ids I need to validate at least.
What is the RoR philosophy on this? I have not seen anything specific to this issue.
Thanks
the URL has parameters if it is a GET url.
Try using POST parameters, which means your url will no longer be cluttered. Note that a malicious user can still send a made-up POST request using curl.
My approach to this is implementing proper authorization. If the user requests information for an object he is not permitted to read, this should be handled by an authorization framework.
With CanCan or Declarative Authorization you can define rules that replace your "manual" (and error-prone) checks in controllers.
I like the IDs being in the URL. That is what REST is about. Getting information for specific Resources, which have to be identified with an ID.
You can use Friendly ID in order to replace the integer ID by a slug (e.g. users/tollbooth instead of users/42).
basically ror routes by default takes id as key to generate urls. If you are not fan of id based urls then you can always override urls by using to_param inside model.
def to_param
# make sure this field is always present & unique
username
end
then by default you will start seeing username instead of id inside urls
How to find object inside controller actions
User.find_by_username(params[:id])
If you dont want to do this manually make use of slug gems like friendly id
I'd like to do the following:
define a before_filter in application.rb that extracts the user's IP address and stores it anywhere, preferably in the session.
define two before filters in all my models as before_create and before_update that add the current user's IP to the object to be stored.
Problem: I cannot access session[] neither env[] in a model. Can anyone help with a standard solution that I don't know yet?
Regards
Jason
Try this. In your user model add a class attribute accessor
cattr_accessor :current_ip
In your application controller add:
before_filter :set_current_ip
protected
def set_current_ip
User.current_ip = request.env['REMOTE_ADDR']
end
Then in your model you should be able to just call User.current_ip
We do something similar to get the current_user object passed through.
You're having trouble doing what you want because Rails is designed not to allow you to have access to session information in your models. It's the classic separation of concerns with MVC. Models are meant to work independently of your other layers, and you'll be thankful they do when you start doing things with Rake or other system tasks where you won't have a session.
The
cattr_accessor :current_ip
is a horrible approach. It's a hack and it should be apparent why. Yes, it may work, but it's the wrong approach to this problem.
Since you're tracking "who" did "what" by their IP, the logical place for this to happen is in the controller layer. There are several approaches you can take, including using CacheSweepers as auditors, as outlined in the Rails Recipes book. CacheSweepers can observe models but also have access to all controller information. Using the ditry attributes in a rails model, you can see exactly what changed.
#user = User.find_by_login "bphogan"
#user.login = "Brian"
#user.save
#user.changed
=> ["login"]
#user.changes
=> {"login"=>["bphogan", "brian"]}
#user.login_was
=> "bphogan"
Combine this with the session info you have and you have a pretty awesome auditor.
Does that help?
If you want to save the IP in the session, you can create a before filter in the applicationController. Like this, for each action, the filter is called and the ip is stored.
authlogic is a plugin to manage users login/sessions etc, it has a built in option to track the users IP
What you really need is a versioning plugin - I suggest having a look at one of the fine solutions at http://ruby-toolbox.com/categories/activerecord_versioning.html
Edit: archived version of that link (was 404 since sometime in 2012): https://web.archive.org/web/20111004161536/http://ruby-toolbox.com:80/categories/activerecord_versioning.html