Is this vulnerable to mass assignment? - ruby-on-rails-3

I use this to allow users to vote on an Entry:
<% form_tag url_for(entry_votes_path(#entry)), :id => 'voting_form', :remote => true do %>
<%= hidden_field_tag 'vote[user_id]', current_user.id %>
<%= submit_tag 'Vote for this entry', :id => 'voting_button' %>
<% end %>
This is my controller code:
def create
#entry = Entry.find(params[:entry_id])
#vote = #entry.votes.build(params[:vote])
respond_to do |format|
if #vote.save
format.html { redirect_to #entry }
format.js
end
end
end
I have two questions
How can I assign current_user.id without using a hidden field?
Also, I'm not using attr_accessible or attr_protected on the Vote model right now. How should I secure the model to make sure someone can't create a lot of votes? Right now, all the fields in the Vote model are set by the params hash -- should I use attr_protected on say, the entry_id foreign key and then set it separately in the controller?

I'm not using attr_accessible
or attr_protected on the Vote model
right now...
Then, by definition, mass assignment is possible from the query string.
Should I use attr_protected on say,
the entry_id foreign key and then set
it separately in the controller?
In general, it's better to use attr_accessible than attr_protected. This is because attr_accessible establishes a default of deny-all to mass assignment and lets you define whitelisted exceptions. On the other hand, attr_protected forces you to blacklist specific attributes. When you modify the program and add a new attribute with attr_accessible set, the program will fail if you need to whitelist the attribute and forget. In other words, it fails safely. Alternatively, if you add a new attribute with attr_protected set, the program will work even if the new attribute should have been included in the blacklist. On other words, it fails insecurely.
The rule here is to protect any attribute which it would be dangerous to allow to be set from the query string. Protecting the key helps prevent injection of new rows but it may be necessary to protect other fields if you want to prevent the ability to change the contents of existing rows.
A good reference on this may be found at guides.rubyonrails.org.

Related

What is the correct way to handle nested forms for join tables in Rails 3.X?

I'm working on a Rails app that ingests external configuration data in the form of a big JSON blob once an hour. My app has services which are acted upon automatically if certain constraints are satisfied. Constraints are specified as a pair (JSON path, acceptable value), and considered satisfied if the current JSON ingestion at the specified path equals the specified value.
Services are not the only things in the database that reference constraints, and each of these different objects can have multiple constraints. For this reason constraints have their own model/table, and are referred to rather than referring to their owners which could be services or other objects. In addition there is a specially treated constraint on a service which controls notification urgency regarding that service, which is also specified as a constraint. Here are my models
class Constraint < ActiveRecord::Base
attr_accessible path, value
end
class ServiceConstraints < ActiveRecord::Base
belongs_to :service
belongs_to :constraint, dependent: :destroy
self.primary_key = :constraint
attr_accesible :service_id, :constraint_id, :constraint_attributes
accepts_nested_attributes_for :constraint
end
class Service < ActiveRecord::Base
#a bunch of other stuff irrelevant to the question
belongs_to :urgency_constraint, class_name: Constraint
has_many :service_constraints, class_name: ServiceConstraint
has_many :constraints, :through => :service_constraints, class_name: Constraint
accepts_nested_attributes_for :urgency_constraint, allow_destroy: true
accepts_nested_attributes_for :service_constraints, allow_destroy: true
attr_accessible :name, :service_constraints_attributes
end
I'm trying to get the forms set up handle this, so that when I edit a service I can add one or more constraints to it directly, i.e. I can click 'add a constraint' and two new fields appear in which the user can enter the constraint path and value. Wash, rinse, repeat as necessary. User's should also be able to change the contents of paths and values of constraints that already exist, and remove constraints entirely. I'm not concerned about duplicate (path, value) pairs in the constraints table.
The forms (in haml)
# app/views/services/_form.haml
= form_for(service) do |f|
-# stuff for the other fields
%table
%thead
%tr
%td Constraint Path
%td Constraint Values
%td
%tbody#service-constraints
= f.fields_for :service_constraints do |ff|
= render 'service_constraint_fields', f: ff
%tfoot
%tr
%td
- sac = service.additional_constraints.new
- ff = instantiate_builder("service[service_constraints_attributes][0][constraint_attributes]", service.service_constraints.new, {})
= link_to 'add a constraint', '#', 'data-insertion-node' => "#service-constraints", 'data-insertion-content' => CGI::escapeHTML(render 'service_constraint_fields', f: ff), 'class' => 'add-nested-fields'
# app/views/services/_service_constraint_fields.haml
%tr.service-constraint-fields
= f.fields_for :constraint do |ff|
%td
= ff.text_field :path
%td
= ff.text_field :value
%td
= link_to "remove", "#", "data-removal-node" => ".service-constraint-fields", "class" => "remove-nested-fields"
I'm trying to do this "the Rails way", which supposedly means I shouldn't need to modify my controller. The controller currently works perfectly as long as the constraints aren't used. Here begin my problems, and I have encountered several while attacking this from various angles
Firstly, as you can see, I'm using the "_service_constraint_fields" partial to populate data-insertion-content which is then dynamically inserted into the page using JavaScript when 'add a constraint' is clicked. But, because the ServiceConstraint object the form_builder is bound to has constraint_id=nil, the fields_for in the partial is iterating over an empty collection, so there are no <td> elements inserted, just an empty <tr>.
Secondly, I added a constraint and service_constraint join row to the database manually. They render in the form correctly, but when I save the form (without any modification), I get this error:
ActiveRecord::RecordNotFound - Couldn't find Constraint with ID=3 for ServiceConstraint with ID=:
This is with the following service_params submitted
{"name"=>"MyBogus", "service_constraints_attributes"=>{"0"=>{"constraint_attributes"=>{"path"=>"a.b.c", "value"=>"some value", "id"=>"3"}, "id"=>""}}}
I can't figure out why the service_constraint id field is blank.

has_and_belongs_to_many validations

What is the most straightforward way to check to make sure a creation of a new record includes the creation of a related record via has_and_belongs_to_many? For example, I have:
class Person < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
has_and_belongs_to_many :people
end
I want a validation to fire on the creation of a new Person to make sure they belong to at least one group.
Also, how would I build this out in the controller? Right now I have:
def create
#person = current_user.people.new(params[:person])
end
I'd like params to include a group hash as well, to act as a sort of nested resource.
I've looked through the Rails documentation and I haven't been able to find anything on this particular case. If someone could explain this to me or point me in the right direction, I'd be very happy. Thanks!
If you want to give the user the option of creating one or more groups during the creation of a person, and then validate that those groups were created, please specify. Otherwise the remainder of this answer will be dedicated to creating a Person and validating that it is associated with at least one existing group.
If you're asking how to verify the existence of an Person-Group association on the groups_people join table, this could be done with weird sql queries and is inadvisable. Just trust that the well tested ActiveRecord works properly.
You can, however, validate the existence of one or more groups on a Person record before it is saved.
As long as you've migrated a join table called groups_people:
# db/migrate/xxxxxxxxxxxxxx_create_groups_people
class CreateGroupsPeople < ActiveRecord::Migration
def change
create_table :groups_people, :id => false do |t|
t.string :group_id, :null => false
t.string :person_id, :null => false
end
end
end
# $ rake db:migrate
, and your controller is correct:
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
def new
#groups = Group.all
#person = Person.new
end
def create
#person= Person.new(params[:person])
if #person.save
# render/redirect_to and/or flash stuff
else
# render and/or flash stuff
end
end
end
, and you've all existing group options as checkboxes:
# app/views/people/new.html.erb
<%= form_for #person do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
# same for other person attributes
<% #groups.each do |g| %>
<%= check_box_tag 'person[group_ids][]', g.id, false, :id => g.group_name_attr %>
<%= label_tag g.group_name_attr %>
<% end %>
<%= f.submit 'Create!' %>
<% end %>
, then you can validate the presence of groups on your Person record:
class Person < ActiveRecord::Base
validates_presence_of :groups
has_and_belongs_to_many :groups
end
There is validates_associated helper, but wouldn't be necessary in this case, where you show Group.all as checkboxed options.
No accepts_nested_attributes_for is necessary for this. It would be if you were creating a Group for a Person while creating a Person. Again, please specify if this is the case.
Just a note: validating an incoming form that includes Group.all as options and gives the option of creating a group along with the person is possible but complicated. It would involve bypassing existing validations on the Group model, if any, which there probably is.

Rails syntax Passing POST parameters from a form to a controller

I'm new to Rails (and fairly new to programming in general) and I am building a web app for myself as a way to learn. Right now I am modifying scaffolded forms and such.
My question is with the "create" method in one of my controllers. There are two entities I am concerned with: the User table and the Habit table. I created a dropdown box in the _form partial for the Habit views to allow a person to select a user from a list of all available when creating a habit as below
<%= collection_select :user, :id, #users, :id, :first_name %>
The habit controller, of course, has
def new
#users = User.all
...
end
This works fine, and when the form submits it posts two hashes of parameters :habit and :user. Now, when I want to process the form input in the create method, I'm not sure how to use the syntax correctly and assign the user_id to the newly create habit. What I WANT to do is something like this
def create
#habit = Habit.new(params[:habit], params[:user])
end
This, of course, is improper syntax.
def create
#habit = Habit.new(params[:habit])
end
assigns the params from the :habit hash correctly, but then the user_id is left unset.
What works is the following, but the code is very lengthy, assigning each value manually.
def create
#habit = Habit.new(:user_id => params[:user][:id],
:description => params[:habit][:description],
:habit_method => params[:habit][:habit_method],
:time_reqd => params[:habit][:time_reqd],
:will_reqd => params[:habit][:will_reqd],
:active => params[:habit][:active])
end
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
Instead of saying Habit.new( <lots of stuff> ), just use Habit.new(params[:habit]). Rails will try to assign each key in the hash (in this case, the params[:habit] hash's keys) to a matching value on the object.
Thus, if params[:habit] has a :description key, it will be assigned to a field called description on your model. This is called mass assignment and is quite handy.
Now you can just do:
#habit = Habit.new(params[:habit])
#habit.user_id = params[:user][:id]
You may want to read the RoR Getting Started Guide, like this section, for more similarly handy features of Rails.
Change
<%= collection_select  :user, :id, #users, :id, :first_name %>
To
<%= collection_select  :habit, :user_id, #users, :id, :first_name %>
The existing scaffold code should just work after that
Alternate
<%= f.select :user_id, #users, :id, :first_name %>

Modeling inheritance with Ruby/Rails ORMs

I'm trying to model this inheritance for a simple blog system
Blog has many Entries, but they may be different in their nature. I don't want to model the Blog table, my concern is about the entries:
simplest entry is an Article that has title and text
Quote, however, does not have a title and has short text
Media has a url and a comment...
etc...
What is a proper way to model this with Ruby on Rails? That is
Should I use ActiverRecord for this or switch to DataMapper?
I would like to avoid the "one big table" approach with lots of empty cells
When I split the data into Entry + PostData, QuoteData etc can I have belongs_to :entry in these Datas without having has_one ??? in the Entry class? That would be standard way to do it in sql and entry.post_data may be resolved by the entry_id in the postdata table.
EDIT: I don't want to model the Blog table, I can do that, my concern is about the entries and how would the inheritance be mapped to the table(s).
I've come across this data problem several times and have tried a few different strategies. I think the one I'm a biggest fan of, is the STI approach as mentioned by cicloon. Make sure you have a type column on your entry table.
class Blog < ActiveRecord::Base
# this is your generic association that would return all types of entries
has_many :entries
# you can also add other associations specific to each type.
# through STI, rails is aware that a media_entry is in fact an Entry
# and will do most of the work for you. These will automatically do what cicloon.
# did manually via his methods.
has_many :articles
has_many :quotes
has_many :media
end
class Entry < ActiveRecord::Base
end
class Article < Entry
has_one :article_data
end
class Quote < Entry
has_one :quote_data
end
class Media < Entry
has_one :media_data
end
class ArticleData < ActiveRecord::Base
belongs_to :article # smart enough to know this is actually an entry
end
class QuoteData < ActiveRecord::Base
belongs_to :quote
end
class MediaData < ActiveRecord::Base
belongs_to :media
end
The thing I like about this approach, is you can keep the generic Entry data in the entry model. Abstract out any of the sub-entry type data into their own data tables, and have a has_one association to them, resulting in no extra columns on your entries table. It also works very well for when you're doing your views:
app/views/articles/_article.html.erb
app/views/quotes/_quote.html.erb
app/views/media/_media.html.erb # may be medium here....
and from your views you can do either:
<%= render #blog.entries %> <!-- this will automatically render the appropriate view partial -->
or have more control:
<%= render #blog.quotes %>
<%= render #blog.articles %>
You can find a pretty generic way of generating forms as well, I usually render the generic entry fields in an entries/_form.html.erb partial. Inside that partial, I also have a
<%= form_for #entry do |f| %>
<%= render :partial => "#{f.object.class.name.tableize}/#{f.object.class.name.underscore}_form", :object => f %>
<% end %>
type render for the sub form data. The sub forms in turn can use accepts_nested_attributes_for + fields_for to get the data passed through properly.
The only pain I have with this approach, is how to handle the controllers and route helpers. Since each entry is of its own type, you'll either have to create custom controllers / routes for each type (you may want this...) or make a generic one. If you take the generic approach, two things to remember.
1) You can't set a :type field through update attributes, your controller will have to instantiate the appropriate Article.new to save it (you may use a factory here).
2) You'll have to use the becomes() method (#article.becomes(Entry)) to work with the entry as an Entry and not a subclass.
Hope this helps.
Warning, I've actually used Media as a model name in the past. In my case it resulted in a table called medias in rails 2.3.x however in rails 3, it wanted my model to be named Medium and my table media. You may have to add a custom Inflection on this naming, though I'm not sure.
You can handle this easily using ActiveRecord STI. It requires you to have a type field in your Entries table. This way you can define your models like this:
def Blog > ActiveRecord::Base
has_many :entries
def articles
entries.where('Type =', 'Article')
end
def quotes
entries.where('Type =', 'Quote')
end
def medias
entries.where('Type =', 'Media')
end
end
def Entry > ActiveRecord::Base
belongs_to :blog
end
def Article > Entry
end
def Quote > Entry
end
def Media > Entry
end

Accept terms of use rails

What is the best way to add a check for accepting terms of use in a rails app?
I can't seem to get validates_acceptance_of working quite right. I added a bool to my user model (was that necessary?). And then have a checkbox that returns either true/false.
I feel like I'm just making a silly little mistake. Any ideas?
In your model,
validates_acceptance_of :terms
If you're using attr_accessible in your model then make sure you also add,
attr_accessible :terms
In your view,
<%= form_for #user do |f| %>
...
<%= f.check_box :terms %>
...
<% end %>
There is no need for an extra column in the users table unless you plan on denying access to users who have not accepted the terms of service, which won't exist since they can't complete registration in the first place.
This is a working Rails 4 solution:
Terms of service doesn't need to be a column in the database
Form
= f.check_box :terms_of_service
models/user.rb
validates :terms_of_service, acceptance: true
And most important, devise will sanitize your parameters and terms of service will be removed from the submitted params. So:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:full_name,
:email, :password, :password_confirmation, :terms_of_service)
end
end
end
This is a working solution for Rails-6.1 (I18n) + Devise-4.8.0 + SimpleForm. No need to add a column in the "users" table in DB.
View
<% label_str = t('read_html', mylink: link_to(t('terms'), '/a/b/c')) %>
<%= f.input :terms_of_service, label: label_str, as: :boolean, checked: false %>
Or, if you use Indo-European languages only, you can make it a little more simple, like:
label_str = (t('agree_html')+link_to(t('terms'), '/a/b/c')+'.').html_safe
/app/models/user.rb
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, on: :create
validates_presence_of :terms_of_service, on: :create
/app/controllers/application_controller.rb
Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up] << :terms_of_service
# see /vendor/bundle/ruby/*/gems/devise-*/lib/devise/parameter_sanitizer.rb
Explanation
In the User model, on: create guarantees it is read only in creation. If you need to reevaluate the condition in updating, too, specify it accordingly, like on: %i(create update).
In the User model, I add validates_presence_of to play safe. The reason is, validates_acceptance_of will not be executed when the parameter terms_of_service is nil, in which case validates_presence_of will catch it and set an error. Admittedly, if the data are always submitted via the web-interface you have built AND your implementation is working perfectly, the value should be always either true or false and never be nil. So, validates_presence_of should not be necessary in this sense. It does no harm, though (except you'd need to be a little careful in manual user creation, bypassing the web-interface, such as from the Console).
The last one is neccesary for use with Devise for the same reason as in the answer by #vladCovaliov; that is, to prevent Devise from sanitizing your custom parameter, which is not a column in the database table. The one-liner in the example above can be stated in any files as long as you are sure it is read at the run-time and after Devise Ruby code. application_controller.rb is one of the sure places (though I guess there is a better-fitting place). Make sure the sentence is put out of the class ApplicationController block.