Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Migrate mass-assignment security to strong_parameters #1609

Merged
merged 4 commits into from

5 participants

@ferrous26
Collaborator

Ahead of the upgrade to Rails 4, this change removes the use of attr_accessible and attr_protected as mass assignment protection mechanisms in favour of whitelisting input attributes at the controller level.

See http://blog.markusproject.org/?p=5588 for more details.

All tests are passing, and some playing around with a local server seemed to work correctly. At this point I have some confidence that this is done correctly.

app/controllers/assignments_controller.rb
@@ -1,3 +1,7 @@
+Dir.glob("app/models/*_submission_rule.rb").each do |rule|
@houndci Collaborator
houndci added a note

Prefer single-quoted strings when you don't need string interpolation or special symbols.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@oneohtrix
Collaborator

So basically all controller actions that have to do with creating and editing resources have parameters go through the whitelist at resource_params, correct?

@ferrous26
Collaborator

@oneohtrix yes, the idea is to make sure that if you are just going to pass an arbitrary list of variables through initialize/update a model, the list should be filtered to only allow attributes which users should be allowed to modify. Obviously not a silver bullet, input still needs to be sanitized; this is just another layer of defence.

Alternatively, models can be, and in some cases are, updated like so https://github.com/MarkUsProject/Markus/blob/d2bcc92788ceed880e113c59ce5bcf4ac3cb7112/app/controllers/annotations_controller.rb#L50, which does not use strong parameters because it makes tis own list of which attributes to allow and extracts those attributes manually.

app/controllers/assignments_controller.rb
((21 lines not shown))
unless potential_rule && potential_rule.ancestors.include?(SubmissionRule)
- raise I18n.t('assignment.not_valid_submission_rule',
- type: params[:assignment][:submission_rule_attributes][:type])
+ raise SubmissionRule::InvalidRuleType.new(rule_name)
@zhangsu Collaborator
zhangsu added a note

The Ruby style guide recommends raising through two arguments to raise instead of explicitly using the initializer:

raise SubmissionRule::InvalidRuleType, rulename
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
app/controllers/assignments_controller.rb
((49 lines not shown))
end
- assignment.submission_rule.type = params[:assignment][:submission_rule_attributes][:type]
+ assignment.submission_rule.type = rule_attributes[:type]
+ assignment.submission_rule.update_attributes(submission_rule_params)
@zhangsu Collaborator
zhangsu added a note

I might have misunderstood this one, but why was the previous code not updating the attributes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@david-yz-liu

@ferrous26 Thanks for doing this!

@zhangsu's last comment about the submission rules prompted me to do some digging... There's a lot of funny stuff going on with the submission rules, but one bug that your PR introduces for sure is that we lose the ability to "Remove" periods (all three rule types). That is, when you click the Remove checkbox and submit, the "Successful update" message is displayed, but the period is still there.

@david-yz-liu

@ferrous26 Here's another fun one: if section due dates are used, every time the settings page gets updated, redundant section due date entries are added into the database, and the redundancies are displayed after the page refreshes.

@ferrous26
Collaborator

So....submission rules are handled by a bunch of hacks:

    # Was the SubmissionRule changed?  If so, switch the type of the
    # SubmissionRule. This little conditional has to do some hack-y
    # workarounds, since accepts_nested_attributes_for is a little...dumb.

I played around with it a bit and found that what happens is that the code expects to nuke every period on a submission rule update, and then create new periods. However, the old periods were referencesd in the submitted parameters, and they would get filtered out by strong_parameters because I did not allow the id parameter on the period of the submission_rule_attributes of the assignment_attributes.

It seems weird that those values are getting submitted if they are not being changed, and they are not coming through with a _destroy marker to indicate they should be deleted. The only solution I can think of right now is just to rewrite the hacks to actually work properly with strong_parameters.

@david-yz-liu
Owner

Yeah, that's probably the best option. Is it possible to revert to the old params passing for the submission rules and merge the other changes, and then continue work on submission rules in another PR?

@ferrous26
Collaborator

Strong parameters is an all or nothing deal. There is an option to simply white list all input or a subset of input (without regard for what that subset actually contains).

Though, I can put the submission rule stuff in a separate commit, if that helps.

app/controllers/assignments_controller.rb
((48 lines not shown))
+ if assignment.submission_rule.class != potential_rule
+
+ # In this case, the easiest thing to do is nuke the old rule along
+ # with all the periods and a new submission rule...this may cause
+ # issues with foreign keys in the future, but not with the current
+ # schema
+ assignment.submission_rule.delete
+ assignment.submission_rule = potential_rule.new
+
+ # this part of the update is particularly hacky, because the incoming
+ # data will include some mix of the old periods and new periods; in
+ # the case of purely new periods the input is only an array, but in
+ # the case of a mixture the input is a hash, and if there are no
+ # periods at all then the periods_attributes will be nil
+ periods = submission_rule_params[:periods_attributes]
+ periods = if periods.kind_of?(Hash)
@houndci Collaborator
houndci added a note

Prefer Object#is_a? over Object#kind_of?.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
app/controllers/assignments_controller.rb
((53 lines not shown))
+ # schema
+ assignment.submission_rule.delete
+ assignment.submission_rule = potential_rule.new
+
+ # this part of the update is particularly hacky, because the incoming
+ # data will include some mix of the old periods and new periods; in
+ # the case of purely new periods the input is only an array, but in
+ # the case of a mixture the input is a hash, and if there are no
+ # periods at all then the periods_attributes will be nil
+ periods = submission_rule_params[:periods_attributes]
+ periods = if periods.kind_of?(Hash)
+ # in this case, we do not care about the keys, because
+ # the new periods will have nonsense values for the key
+ # and the old periods are being discarded
+ periods.map { |_, p| p }.reject { |p| p.has_key?(:id) }
+ elsif periods.kind_of?(Array)
@houndci Collaborator
houndci added a note

Prefer Object#is_a? over Object#kind_of?.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ferrous26
Collaborator

@david-yz-liu GH-1622 and all submission rule updating funkiness that I could find has been addressed in c40f051

ferrous26 added some commits
@ferrous26 ferrous26 Migrate mass-assignment security to strong_parameters
Ahead of the upgrade to Rails 4, this change removes the use of
attr_accessible and attr_protected as mass assignment protection
mechanisms in favour of whitelisting input attributes at the
controller level.

See http://blog.markusproject.org/?p=5588 for more details.
fe7928e
@ferrous26 ferrous26 Handle submission rule changes correctly in assignment editing
Resolves GH-1622.
5d1c404
@david-yz-liu
Owner

@ferrous26 Nice job, thanks! Once you fix the bug with the dubplicating Section Due Dates, I will merge this.

@ferrous26 ferrous26 Fix section due date duplication
They were not being properly whitelisted previously, and the
workaround did not check if the due dates already existed before
creating new due dates.

This fix allows Rails to simply Do The Right Thing.
63c2793
@ferrous26
Collaborator
@ferrous26
Collaborator

There are some test failures now, but they appear to be false positives. I am looking into it now.

test/functional/assignments_controller_test.rb
((25 lines not shown))
assert_not_nil new_assignment
assert_equal true, new_assignment.section_due_dates_type
due_date_attributes.each do |key, value|
- assert new_assignment.section_due_dates[key].
- due_date.to_s.include?(value['due_date'])
+ date = Time.parse(value['due_date'])
+ due_date =
+ new_assignment.section_due_dates
+ .find { |d| d.due_date == date}
@houndci Collaborator
houndci added a note

Prefer detect over find.
Space missing inside }.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
test/functional/assignments_controller_test.rb
((6 lines not shown))
due_date_attributes = {
- 0 => { 'section_id' => "#{@section1.id}",
- 'due_date' => '2011-10-27 00:00' },
- 1 => { 'section_id' => "#{@section2.id}",
- 'due_date' => '2011-10-29 00:00' }
+ '0' => { 'section_id' => "#{@section1.id}",
@houndci Collaborator
houndci added a note

Use 2 spaces for indentation in a hash, relative to the start of the line where the left curly brace is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
test/functional/assignments_controller_test.rb
((6 lines not shown))
due_date_attributes = {
- 0 => { 'section_id' => "#{@section1.id}",
- 'due_date' => nil },
- 1 => { 'section_id' => "#{@section2.id}",
- 'due_date' => '2011-10-29 00:00' }
+ '0' => { 'section_id' => "#{@section1.id}",
@houndci Collaborator
houndci added a note

Use 2 spaces for indentation in a hash, relative to the start of the line where the left curly brace is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ferrous26 ferrous26 Really delete old section due dates when they get turned off
And fix broken tests which were providing false failures.
1172027
@houndci houndci commented on the diff
test/functional/assignments_controller_test.rb
((23 lines not shown))
assert_not_nil new_assignment
assert_equal true, new_assignment.section_due_dates_type
due_date_attributes.each do |key, value|
- assert new_assignment.section_due_dates[key].
- due_date.to_s.include?(value['due_date'])
+ date = Time.parse(value['due_date'])
+ due_date =
+ new_assignment.section_due_dates
+ .find { |d| d.due_date == date }
@houndci Collaborator
houndci added a note

Prefer detect over find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ferrous26
Collaborator

Ok, all fixed up now.

Though, I strongly disagree with Hound on the matter of #find vs. #detect. When did #detect get in vogue?

@david-yz-liu david-yz-liu merged commit 89a2a30 into MarkUsProject:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 5, 2014
  1. @ferrous26

    Migrate mass-assignment security to strong_parameters

    ferrous26 authored
    Ahead of the upgrade to Rails 4, this change removes the use of
    attr_accessible and attr_protected as mass assignment protection
    mechanisms in favour of whitelisting input attributes at the
    controller level.
    
    See http://blog.markusproject.org/?p=5588 for more details.
  2. @ferrous26
  3. @ferrous26

    Fix section due date duplication

    ferrous26 authored
    They were not being properly whitelisted previously, and the
    workaround did not check if the due dates already existed before
    creating new due dates.
    
    This fix allows Rails to simply Do The Right Thing.
Commits on Aug 6, 2014
  1. @ferrous26

    Really delete old section due dates when they get turned off

    ferrous26 authored
    And fix broken tests which were providing false failures.
This page is out of date. Refresh to see the latest.
Showing with 352 additions and 157 deletions.
  1. +1 −0  Gemfile
  2. +6 −0 Gemfile.lock
  3. +9 −4 app/controllers/admins_controller.rb
  4. +16 −4 app/controllers/annotation_categories_controller.rb
  5. +110 −34 app/controllers/assignments_controller.rb
  6. +10 −2 app/controllers/flexible_criteria_controller.rb
  7. +12 −3 app/controllers/grade_entry_forms_controller.rb
  8. +6 −2 app/controllers/notes_controller.rb
  9. +9 −1 app/controllers/results_controller.rb
  10. +20 −2 app/controllers/rubrics_controller.rb
  11. +8 −2 app/controllers/sections_controller.rb
  12. +12 −4 app/controllers/students_controller.rb
  13. +8 −3 app/controllers/tas_controller.rb
  14. +16 −19 app/helpers/grade_entry_forms_helper.rb
  15. +6 −2 app/models/submission_rule.rb
  16. +0 −3  app/models/test_file.rb
  17. +3 −0  config/application.rb
  18. +0 −3  config/environments/development.rb
  19. +0 −3  config/environments/test.rb
  20. +1 −1  test/functional/admins_controller_test.rb
  21. +39 −30 test/functional/annotation_categories_controller_test.rb
  22. +47 −30 test/functional/assignments_controller_test.rb
  23. +7 −0 test/functional/authenticated_controller_test.rb
  24. +6 −5 test/functional/students_controller_test.rb
View
1  Gemfile
@@ -23,6 +23,7 @@ gem 'coffee-script'
gem 'jquery-rails'
gem 'prototype-rails' # FIXME: Will be needed with Rails3.1
gem 'activerecord-import'
+gem 'strong_parameters' # NOTE: this goes away when upgrading to Rails4
group :assets do
gem 'tilt', '~> 1.3.7'
View
6 Gemfile.lock
@@ -169,6 +169,11 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.9)
+ strong_parameters (0.2.3)
+ actionpack (~> 3.0)
+ activemodel (~> 3.0)
+ activesupport (~> 3.0)
+ railties (~> 3.0)
therubyracer (0.12.1)
libv8 (~> 3.16.14.0)
ref
@@ -231,6 +236,7 @@ DEPENDENCIES
shoulda
simplecov
sqlite3
+ strong_parameters
therubyracer
thin
tilt (~> 1.3.7)
View
13 app/controllers/admins_controller.rb
@@ -19,14 +19,13 @@ def edit
end
def new
- @user = Admin.new(params[:user])
+ @user = Admin.new(user_params)
end
def update
@user = Admin.find(params[:id])
- attrs = params[:user]
# update_attributes supplied by ActiveRecords
- if @user.update_attributes(attrs).nil?
+ if @user.update_attributes(user_params).nil?
flash[:error] = I18n.t('admins.update.error')
render :edit
else
@@ -41,7 +40,7 @@ def create
# Default attributes: role = TA or role = STUDENT
# params[:user] is a hash of values passed to the controller
# by the HTML form with the help of ActiveView::Helper::
- @user = Admin.new(params[:user])
+ @user = Admin.new(user_params)
# Return unless the save is successful; save inherted from
# active records--creates a new record if the model is new, otherwise
# updates the existing record
@@ -55,4 +54,10 @@ def create
render 'new'
end
end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:user_name, :first_name, :last_name)
+ end
end
View
20 app/controllers/annotation_categories_controller.rb
@@ -21,7 +21,7 @@ def add_annotation_category
# Attempt to add Annotation Category
@annotation_categories = @assignment.annotation_categories
@annotation_category = AnnotationCategory.new
- @annotation_category.update_attributes(params[:annotation_category])
+ @annotation_category.update_attributes(annotation_category_params)
@annotation_category.assignment = @assignment
unless @annotation_category.save
render :new_annotation_category_error
@@ -35,7 +35,7 @@ def update_annotation_category
@assignment = Assignment.find(params[:assignment_id])
@annotation_category = AnnotationCategory.find(params[:id])
- @annotation_category.update_attributes(params[:annotation_category])
+ @annotation_category.update_attributes(annotation_category_params)
if @annotation_category.save
flash.now[:success] = I18n.t('annotations.update.annotation_category_success')
else
@@ -45,7 +45,7 @@ def update_annotation_category
def update_annotation
@annotation_text = AnnotationText.find(params[:id])
- @annotation_text.update_attributes(params[:annotation_text])
+ @annotation_text.update_attributes(annotation_text_params)
@annotation_text.last_editor_id = current_user.id
@annotation_text.save
end
@@ -55,7 +55,7 @@ def add_annotation_text
if request.post?
# Attempt to add Annotation Text
@annotation_text = AnnotationText.new
- @annotation_text.update_attributes(params[:annotation_text])
+ @annotation_text.update_attributes(annotation_text_params)
@annotation_text.annotation_category = @annotation_category
@annotation_text.creator_id = current_user.id
@annotation_text.last_editor_id = current_user.id
@@ -187,4 +187,16 @@ def yml_upload
end
redirect_to action: 'index', assignment_id: @assignment.id
end
+
+ private
+
+ def annotation_category_params
+ # we do not want to allow :position to be given directly
+ params.require(:annotation_category)
+ .permit(:annotation_category_name, :assignment_id)
+ end
+
+ def annotation_text_params
+ params.require(:annotation_text).permit(:content)
+ end
end
View
144 app/controllers/assignments_controller.rb
@@ -1,3 +1,10 @@
+# We need to force loading of all submission rules so that methods
+# like .const_defined? work correctly (rails abuse of autoload was
+# causing issues)
+Dir.glob('app/models/*_submission_rule.rb').each do |rule|
+ require File.expand_path(rule)
+end
+
class AssignmentsController < ApplicationController
before_filter :authorize_only_for_admin,
except: [:deletegroup,
@@ -226,11 +233,13 @@ def update
end
begin
- @assignment = process_assignment_form(@assignment, params)
- rescue Exception, RuntimeError => e
- @assignment.errors.add(:base, I18n.t('assignment.error',
- message: e.message))
- render :edit, id: @assignment.id
+ @assignment.transaction do
+ @assignment = process_assignment_form(@assignment)
+ end
+ rescue SubmissionRule::InvalidRuleType => e
+ @assignment.errors.add(:base, I18n.t('assignment.error',
+ message: e.message))
+ render :edit, id: @assignment.id
return
end
@@ -264,9 +273,10 @@ def new
def create
@assignment = Assignment.new
@assignment.build_assignment_stat
+ @assignment.build_submission_rule
@assignment.transaction do
begin
- @assignment = process_assignment_form(@assignment, params)
+ @assignment = process_assignment_form(@assignment)
rescue Exception, RuntimeError => e
@assignment.errors.add(:base, e.message)
end
@@ -599,12 +609,12 @@ def update_assignment!(map)
flash[:success] = t('assignment.create_success')
end
- def process_assignment_form(assignment, params)
- assignment.attributes = params[:assignment]
+ def process_assignment_form(assignment)
+ assignment.update_attributes(assignment_params)
# if there are no section due dates, destroy the objects that were created
if params[:assignment][:section_due_dates_type] == '0'
- assignment.section_due_dates.each { |s| s.destroy }
+ assignment.section_due_dates.each(&:destroy)
assignment.section_due_dates_type = false
assignment.section_groups_only = false
else
@@ -612,41 +622,60 @@ def process_assignment_form(assignment, params)
assignment.section_groups_only = true
end
- # Some protective measures here to make sure we haven't been duped...
- rule_name = params[:assignment][:submission_rule_attributes][:type]
- potential_rule = Module.const_get(rule_name) rescue nil
+ # Due to some funkiness, we need to handle submission rules separately
+ # from the main attribute update
+
+ # First, figure out what kind of rule has been requested
+ rule_attributes = params[:assignment][:submission_rule_attributes]
+ rule_name = rule_attributes[:type]
+ potential_rule = if SubmissionRule.const_defined?(rule_name)
+ SubmissionRule.const_get(rule_name)
+ end
unless potential_rule && potential_rule.ancestors.include?(SubmissionRule)
- raise I18n.t('assignment.not_valid_submission_rule',
- type: params[:assignment][:submission_rule_attributes][:type])
+ raise SubmissionRule::InvalidRuleType, rule_name
end
- # Was the SubmissionRule changed? If so, switch the type of the SubmissionRule.
- # This little conditional has to do some hack-y workarounds, since
- # accepts_nested_attributes_for is a little...dumb.
- if assignment.submission_rule.attributes['type'] !=
- params[:assignment][:submission_rule_attributes][:type]
-
- # delete all the previously created periods for the given submission_rule
- # note that we should not delete this if the current submission rule
- # cannot be saved ( not valid )
- # otherwise we would end up with no periods!
- if assignment.submission_rule.valid?
- assignment.submission_rule.periods.where('id != ?',
- assignment.submission_rule.id).
- delete_all
+ # If the submission rule was changed, we need to do a more complicated
+ # dance with the database in order to get things updated.
+ if assignment.submission_rule.class != potential_rule
+
+ # In this case, the easiest thing to do is nuke the old rule along
+ # with all the periods and a new submission rule...this may cause
+ # issues with foreign keys in the future, but not with the current
+ # schema
+ assignment.submission_rule.delete
+ assignment.submission_rule = potential_rule.new
+
+ # this part of the update is particularly hacky, because the incoming
+ # data will include some mix of the old periods and new periods; in
+ # the case of purely new periods the input is only an array, but in
+ # the case of a mixture the input is a hash, and if there are no
+ # periods at all then the periods_attributes will be nil
+ periods = submission_rule_params[:periods_attributes]
+ periods = case periods
+ when Hash
+ # in this case, we do not care about the keys, because
+ # the new periods will have nonsense values for the key
+ # and the old periods are being discarded
+ periods.map { |_, p| p }.reject { |p| p.has_key?(:id) }
+ when Array
+ periods
+ else
+ []
+ end
+ # now that we know what periods we want to keep, we can create them
+ periods.each do |p|
+ assignment.submission_rule.periods << Period.new(p)
end
- assignment.submission_rule.type = params[:assignment][:submission_rule_attributes][:type]
- # add the submission type for validate in the model
- assignment.submission_rule.periods.each do |period|
- period.submission_rule_type = assignment.submission_rule.type
- end
+ else # in this case Rails does what we want, so we'll take the easy route
+ assignment.submission_rule.update_attributes(submission_rule_params)
end
if params[:is_group_assignment] == 'true'
# Is the instructor forming groups?
- if params[:assignment][:student_form_groups] == '0'
+ if assignment_params[:student_form_groups] == '0'
assignment.invalid_override = true
else
assignment.student_form_groups = true
@@ -659,6 +688,7 @@ def process_assignment_form(assignment, params)
assignment.group_min = 1
assignment.group_max = 1
end
+
assignment
end
@@ -698,4 +728,50 @@ def manually_collect_and_prepare_test(grouping, revision_number)
end
end
+ private
+
+ def assignment_params
+ params.require(:assignment).permit(
+ :short_identifier,
+ :description,
+ :message,
+ :repository_folder,
+ :due_date,
+ :allow_web_submits,
+ :display_grader_names_to_students,
+ :is_hidden,
+ :marking_scheme_type,
+ :group_min,
+ :group_max,
+ :student_form_groups,
+ :group_name_autogenerated,
+ :allow_remarks,
+ :remark_due_date,
+ :remark_message,
+ :section_groups_only,
+ :enable_test,
+ :assign_graders_to_criteria,
+ :tokens_per_day,
+ :group_name_displayed,
+ :invalid_override,
+ :section_groups_only,
+ section_due_dates_attributes: [:_destroy,
+ :id,
+ :section_id,
+ :due_date],
+ assignment_files_attributes: [:_destroy,
+ :id,
+ :filename]
+ )
+ end
+
+ def submission_rule_params
+ params.require(:assignment)
+ .require(:submission_rule_attributes)
+ .permit(:_destroy, :id, periods_attributes: [:id,
+ :deduction,
+ :interval,
+ :hours,
+ :_destroy])
+ end
end
View
12 app/controllers/flexible_criteria_controller.rb
@@ -19,7 +19,7 @@ def edit
def update
@criterion = FlexibleCriterion.find(params[:id])
- unless @criterion.update_attributes(params[:flexible_criterion])
+ unless @criterion.update_attributes(flexible_criterion_params)
render :errors
return
end
@@ -43,7 +43,7 @@ def create
@criterion.assignment = @assignment
@criterion.max = FlexibleCriterion::DEFAULT_MAX
@criterion.position = new_position
- unless @criterion.update_attributes(params[:flexible_criterion])
+ unless @criterion.update_attributes(flexible_criterion_params)
@errors = @criterion.errors
render :add_criterion_error
return
@@ -115,4 +115,12 @@ def update_positions
# end
end
+ private
+
+ def flexible_criterion_params
+ params.require(:flexible_criterion).permit(:flexible_criterion_name,
+ :description,
+ :position,
+ :max)
+ end
end
View
15 app/controllers/grade_entry_forms_controller.rb
@@ -62,7 +62,7 @@ def create
# Process input properties
@grade_entry_form.transaction do
# Edit params before updating model
- new_params = update_grade_entry_form_params params[:grade_entry_form]
+ new_params = update_grade_entry_form_params grade_entry_form_params
if @grade_entry_form.update_attributes(new_params)
# Success message
flash[:success] = I18n.t('grade_entry_forms.create.success')
@@ -85,7 +85,7 @@ def update
@grade_entry_form.transaction do
# Edit params before updating model
- new_params = update_grade_entry_form_params params[:grade_entry_form]
+ new_params = update_grade_entry_form_params grade_entry_form_params
if @grade_entry_form.update_attributes(new_params)
# Success message
flash[:success] = I18n.t('grade_entry_forms.edit.success')
@@ -355,7 +355,16 @@ def csv_upload
end
end
end
- redirect_to action: 'grades', id: @grade_entry_form.id
+ redirect_to action: 'grades', id: @grade_entry_form.id
end
+ private
+
+ def grade_entry_form_params
+ params.require(:grade_entry_form).permit(:description,
+ :message,
+ :date,
+ :show_total,
+ :short_identifier)
+ end
end
View
8 app/controllers/notes_controller.rb
@@ -54,7 +54,7 @@ def new
end
def create
- @note = Note.new(params[:note])
+ @note = Note.new(notes_params)
@note.noteable_type = params[:noteable_type]
@note.creator_id = @current_user.id
@@ -96,7 +96,7 @@ def edit
end
def update
- if @note.update_attributes(params[:note])
+ if @note.update_attributes(notes_params)
flash[:success] = I18n.t('notes.update.success')
redirect_to action: 'index'
else
@@ -116,6 +116,7 @@ def destroy
end
private
+
def retrieve_groupings(assignment)
if assignment.nil?
@groupings = Array.new
@@ -142,4 +143,7 @@ def ensure_can_modify
end
end
+ def notes_params
+ params.require(:note).permit(:notes_message, :noteable_id)
+ end
end
View
10 app/controllers/results_controller.rb
@@ -427,7 +427,7 @@ def add_extra_mark
@extra_mark = ExtraMark.new
@extra_mark.result = @result
@extra_mark.unit = ExtraMark::UNITS[:points]
- if @extra_mark.update_attributes(params[:extra_mark])
+ if @extra_mark.update_attributes(extra_mark_params)
# need to re-calculate total mark
@result.update_total_mark
render template: 'results/marker/insert_extra_mark'
@@ -541,4 +541,12 @@ def authorized_to_download?(map)
def update_remark_request_count
Assignment.find(params[:assignment_id]).update_remark_request_count
end
+
+ private
+
+ def extra_mark_params
+ params.require(:extra_mark).permit(:result,
+ :description,
+ :extra_mark)
+ end
end
View
22 app/controllers/rubrics_controller.rb
@@ -14,7 +14,7 @@ def edit
def update
@criterion = RubricCriterion.find(params[:id])
- unless @criterion.update_attributes(params[:rubric_criterion])
+ unless @criterion.update_attributes(rubric_criterion_params)
render :errors
return
end
@@ -40,7 +40,7 @@ def create
@criterion.weight = RubricCriterion::DEFAULT_WEIGHT
@criterion.set_default_levels
@criterion.position = new_position
- unless @criterion.update_attributes(params[:rubric_criterion])
+ unless @criterion.update_attributes(rubric_criterion_params)
@errors = @criterion.errors
render 'add_criterion_error', formats: [:js]
return
@@ -173,4 +173,22 @@ def update_positions
end
end
+ private
+
+ def rubric_criterion_params
+ params.require(:rubric_criterion).permit(:rubric_criterion_name,
+ :assignment,
+ :position,
+ :level_0_name,
+ :level_0_description,
+ :level_1_name,
+ :level_1_description,
+ :level_2_name,
+ :level_2_description,
+ :level_3_name,
+ :level_3_description,
+ :level_4_name,
+ :level_4_description,
+ :weight)
+ end
end
View
10 app/controllers/sections_controller.rb
@@ -16,7 +16,7 @@ def new
# Creates a new section
def create
- @section = Section.new(params[:section])
+ @section = Section.new(section_params)
if @section.save
@sections = Section.all
flash[:success] = I18n.t('section.create.success', name: @section.name)
@@ -43,7 +43,7 @@ def edit
def update
@section = Section.find(params[:id])
- if @section.update_attributes(params[:section])
+ if @section.update_attributes(section_params)
flash[:success] = I18n.t('section.update.success', name: @section.name)
redirect_to action: 'index'
else
@@ -68,4 +68,10 @@ def destroy
end
redirect_to action: :index
end
+
+ private
+
+ def section_params
+ params.require(:section).permit(:name)
+ end
end
View
16 app/controllers/students_controller.rb
@@ -39,9 +39,8 @@ def edit
def update
@user = Student.find_by_id(params[:id])
- attrs = params[:user]
# update_attributes supplied by ActiveRecords
- if @user.update_attributes(attrs)
+ if @user.update_attributes(user_params)
flash[:success] = I18n.t('students.update.success',
user_name: @user.user_name)
redirect_to action: 'index'
@@ -80,7 +79,7 @@ def bulk_modify
end
def new
- @user = Student.new(params[:user])
+ @user = Student.new(user_params)
@sections = Section.all(order: 'name')
end
@@ -88,7 +87,7 @@ def create
# Default attributes: role = TA or role = STUDENT
# params[:user] is a hash of values passed to the controller
# by the HTML form with the help of ActiveView::Helper::
- @user = Student.new(params[:user])
+ @user = Student.new(user_params)
if @user.save
flash[:success] = I18n.t('students.create.success',
user_name: @user.user_name)
@@ -151,4 +150,13 @@ def delete_grace_period_deduction
@grace_period_deductions = student.grace_period_deductions
end
+ private
+
+ def user_params
+ params.require(:user).permit(:user_name,
+ :last_name,
+ :first_name,
+ :grace_credits,
+ :section_id)
+ end
end
View
11 app/controllers/tas_controller.rb
@@ -25,9 +25,8 @@ def edit
def update
@user = Ta.find_by_id(params[:user][:id])
- attrs = params[:user]
# update_attributes supplied by ActiveRecords
- if @user.update_attributes(attrs)
+ if @user.update_attributes(user_params)
flash[:success] = I18n.t('tas.update.success',
user_name: @user.user_name)
@@ -42,7 +41,7 @@ def create
# Default attributes: role = TA or role = STUDENT
# params[:user] is a hash of values passed to the controller
# by the HTML form with the help of ActiveView::Helper::
- @user = Ta.new(params[:user])
+ @user = Ta.new(user_params)
# Return unless the save is successful; save inherted from
# active records--creates a new record if the model is new, otherwise
# updates the existing record
@@ -90,4 +89,10 @@ def upload_ta_list
end
redirect_to action: 'index'
end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:user_name, :last_name, :first_name)
+ end
end
View
35 app/helpers/grade_entry_forms_helper.rb
@@ -44,39 +44,36 @@ def set_release_on_grade_entry_students(grade_entry_students, release, errors)
# Removes items that have empty names (so they don't get updated)
def update_grade_entry_form_params(attributes)
- @grade_entry_items = attributes[:grade_entry_items_attributes]
+ grade_entry_items =
+ params[:grade_entry_form][:grade_entry_items_attributes]
- if @grade_entry_items == nil
+ if grade_entry_items == nil
return attributes
end
# Find the largest position that has been set
max_position = 0
- @grade_entry_items.each do |key, value|
- unless value == nil
- this_position = value[:position]
- if this_position && this_position.to_i > max_position
- max_position = this_position.to_i
- end
- end
+ grade_entry_items.each do |key, value|
+ next unless value
+ this_position = value[:position]
+ next unless this_position && this_position.to_i > max_position
+ max_position = this_position.to_i
end
# Update the attributes hash
max_position += 1
- @grade_entry_items.sort.each do |item|
+ grade_entry_items.sort.each do |item|
# Items not added don't have a name
# Some items are being deleted so don't update those
- if item[1][:name] and item[1][:destroy] != 1
- # If the set position is not valid, update it
- unless @grade_entry_items[item[0]][:position].to_i > 0
- @grade_entry_items[item[0]][:position] = max_position
- max_position += 1
- end
- end
+ next if item[1][:name] && item[1][:destroy] == 1
+ # If the set position is not valid, update it
+ next if grade_entry_items[item[0]][:position].to_i > 0
+ grade_entry_items[item[0]][:position] = max_position
+ max_position += 1
end
- attributes[:grade_entry_items_attributes] = @grade_entry_items
- return attributes
+ attributes[:grade_entry_items_attributes] = grade_entry_items
+ attributes
end
def sort_items_by_position(items)
View
8 app/models/submission_rule.rb
@@ -1,11 +1,15 @@
class SubmissionRule < ActiveRecord::Base
+ class InvalidRuleType < Exception
+ def initialize(rule_name)
+ super I18n.t('assignment.not_valid_submission_rule', rule_name)
+ end
+ end
+
belongs_to :assignment
has_many :periods, dependent: :destroy, order: 'id'
accepts_nested_attributes_for :periods, allow_destroy: true
- attr_accessible :type, :periods_attributes
-
# validates_associated :assignment
# validates_presence_of :assignment
View
3  app/models/test_file.rb
@@ -1,9 +1,6 @@
class TestFile < ActiveRecord::Base
belongs_to :assignment
- # Restrict updates to filename, filetype and is_private columns
- attr_accessible :filename, :filetype, :is_private
-
# Run sanitize_filename before saving to the database
before_save :sanitize_filename
View
3  config/application.rb
@@ -19,6 +19,9 @@ class Application < Rails::Application
# Javascripts files always loaded in views
config.action_view.javascript_expansions[:defaults] = %w(prototype rails application )
+ # NOTE: this should be removed when upgrading to Rails 4
+ config.active_record.whitelist_attributes = false
+
# Set this if MarkUs is not hosted under / of your Web-host.
# E.g. if MarkUs should be accessible by http://yourhost.com/markus/instance0
# then set the below directive to "/markus/instance0".
View
3  config/environments/development.rb
@@ -31,9 +31,6 @@
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
- # Raise exception on mass assignment protection for Active Record models
- config.active_record.mass_assignment_sanitizer = :strict
-
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
config.active_record.auto_explain_threshold_in_seconds = 1.0
View
3  config/environments/test.rb
@@ -28,9 +28,6 @@
# Show Deprecated Warnings (to :log or to :stderr)
config.active_support.deprecation = :stderr
- # Raise exception on mass assignment protection for Active Record models
- config.active_record.mass_assignment_sanitizer = :strict
-
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
config.active_record.auto_explain_threshold_in_seconds = 1.0
View
2  test/functional/admins_controller_test.rb
@@ -25,7 +25,7 @@ def teardown
end
should 'be able to get :new' do
- get_as @admin, :new
+ get_as @admin, :new, user_params
assert_response :success
end
View
69 test/functional/annotation_categories_controller_test.rb
@@ -124,9 +124,15 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
@category = AnnotationCategory.make
@assignment = @category.assignment
@annotation_text = AnnotationText.make(
- :annotation_category => @category,
- :creator_id => @admin.id,
- :last_editor_id => @admin.id)
+ annotation_category: @category,
+ creator_id: @admin.id,
+ last_editor_id: (@admin.id + 1))
+ @annotation_text_params = {
+ id: @annotation_text.id,
+ annotation_category: @annotation_text.annotation_category,
+ creator_id: @annotation_text.creator_id,
+ last_editor_id: @annotation_text.last_editor_id
+ }
end
should 'on :index' do
@@ -166,9 +172,10 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
should 'update properly' do
get_as @admin,
:update_annotation_category,
- :assignment_id => @assignment.id,
- :id => @category.id,
- :format => :js
+ assignment_id: @assignment.id,
+ id: @category.id,
+ annotation_category: { annotation_category_name: 'Test' },
+ format: :js
assert_response :success
assert_not_nil assigns :annotation_category
assert_equal I18n.t('annotations.update.annotation_category_success'),
@@ -179,10 +186,11 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
AnnotationCategory.any_instance.stubs(:save).returns(false)
get_as @admin,
- :update_annotation_category,
- :assignment_id => @assignment.id,
- :id => @category.id,
- :format => :js
+ :update_annotation_category,
+ assignment_id: @assignment.id,
+ id: @category.id,
+ annotation_category: { annotation_category_name: 'Test' },
+ format: :js
assert_response :success
assert_not_nil flash[:error]
assert_nil flash[:success]
@@ -191,28 +199,27 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
end
should 'on :update_annotation' do
- AnnotationText.any_instance.expects(:update_attributes).with(
- @annotation_text)
- AnnotationText.any_instance.expects(:save).once
+ refute_equal @admin.id,
+ AnnotationText.find(@annotation_text.id).last_editor_id
get_as @admin,
- :update_annotation,
- :assignment_id => 1,
- :id => @annotation_text.id,
- :annotation_text => @annotation_text,
- :format => :js
+ :update_annotation,
+ assignment_id: 1,
+ id: @annotation_text.id,
+ annotation_text: @annotation_text_params,
+ format: :js
assert_response :success
+ assert_equal @admin.id,
+ AnnotationText.find(@annotation_text.id).last_editor_id
end
context 'As another admin' do
- should 'update last_editor_id with editor.id' do
- AnnotationText.any_instance.expects(:update_attributes).with(
- @annotation_text)
- get_as @editor,
- :update_annotation,
- :assignment_id => 1,
- :id => @annotation_text.id,
- :annotation_text => @annotation_text,
- :format => :js
+ should 'update last_editor_id with editor.id' do
+ get_as @editor,
+ :update_annotation,
+ assignment_id: 1,
+ id: @annotation_text.id,
+ annotation_text: @annotation_text_params,
+ format: :js
@annotation_text = AnnotationText.find(@annotation_text.id)
assert_response :success
assert_equal @editor.id, @annotation_text.last_editor_id
@@ -301,6 +308,7 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
post_as @admin,
:add_annotation_category,
assignment_id: @assignment.id,
+ annotation_category: { annotation_category_name: 'Test' },
format: :js
assert_response :success
assert_not_nil assigns :assignment
@@ -326,9 +334,10 @@ class AnnotationCategoriesControllerTest < AuthenticatedControllerTest
should 'with errors on save' do
AnnotationText.any_instance.stubs(:save).returns(false)
post_as @admin, :add_annotation_text,
- :assignment_id => 1,
- :id => @category.id,
- :format => :js
+ assignment_id: 1,
+ id: @category.id,
+ annotation_text: @annotation_text_params,
+ format: :js
assert_response :success
assert render_template 'new_annotation_text_error'
assert_not_nil assigns :annotation_category
View
77 test/functional/assignments_controller_test.rb
@@ -57,7 +57,7 @@ def teardown
post_as @admin, :create, @attributes
new_assignment = Assignment.find_by_short_identifier(@short_identifier)
assert_not_nil new_assignment
- assert redirect_to(:action => 'edit', :id => new_assignment)
+ assert redirect_to(action: 'edit', id: new_assignment)
end
should 'have an assignment stat object associated' do
@@ -71,7 +71,7 @@ def teardown
should "set the flash's success message" do
post_as @admin, :create, @attributes
new_assignment = Assignment.find_by_short_identifier(@short_identifier)
- assert_equal flash[:success], 'Successfully created the assignment'
+ assert_equal 'Successfully created the assignment', flash[:success]
end
context 'with section due dates' do
@@ -81,46 +81,60 @@ def teardown
end
should 'be able to create assignment with section due dates' do
- @attributes['section_due_dates_type'] = '1'
+ @attributes['assignment']['section_due_dates_type'] = '1'
due_date_attributes = {
- 0 => { 'section_id' => "#{@section1.id}",
+ '0' => { 'section_id' => "#{@section1.id}",
'due_date' => '2011-10-27 00:00' },
- 1 => { 'section_id' => "#{@section2.id}",
+ '1' => { 'section_id' => "#{@section2.id}",
'due_date' => '2011-10-29 00:00' }
}
- @attributes['assignment']['section_due_dates_attributes'] = due_date_attributes
+ @attributes['assignment']['section_due_dates_attributes'] =
+ due_date_attributes
post_as @admin, :create, @attributes
- new_assignment = Assignment.find_by_short_identifier(@short_identifier)
+ new_assignment =
+ Assignment.where(short_identifier: @short_identifier).first
+
assert_not_nil new_assignment
assert_equal true, new_assignment.section_due_dates_type
due_date_attributes.each do |key, value|
- assert new_assignment.section_due_dates[key].
- due_date.to_s.include?(value['due_date'])
+ date = Time.parse(value['due_date'])
+ due_date =
+ new_assignment.section_due_dates
+ .find { |d| d.due_date == date }
@houndci Collaborator
houndci added a note

Prefer detect over find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ refute_nil due_date, "Due date not added for section #{key}"
+ assert_equal value['section_id'].to_i, due_date.section_id
end
- assert redirect_to(:action => 'edit', :id => new_assignment)
+ assert redirect_to(action: 'edit', id: new_assignment)
end
should 'be able to create assignment due date with some section due dates set' do
# A section due date can be nil
# That section then uses the main due_date
- @attributes['section_due_dates_type'] = '1'
+ @attributes['assignment']['section_due_dates_type'] = '1'
due_date_attributes = {
- 0 => { 'section_id' => "#{@section1.id}",
+ '0' => { 'section_id' => "#{@section1.id}",
'due_date' => nil },
- 1 => { 'section_id' => "#{@section2.id}",
+ '1' => { 'section_id' => "#{@section2.id}",
'due_date' => '2011-10-29 00:00' }
}
- @attributes['assignment']['section_due_dates_attributes'] = due_date_attributes
+ @attributes['assignment']['section_due_dates_attributes'] =
+ due_date_attributes
post_as @admin, :create, @attributes
- new_assignment = Assignment.find_by_short_identifier(@short_identifier)
+
+ new_assignment =
+ Assignment.where(short_identifier: @short_identifier).first
+
assert_not_nil new_assignment
assert_equal true, new_assignment.section_due_dates_type
assert_nil new_assignment.section_due_dates[0].due_date
- assert new_assignment.section_due_dates[1].
- due_date.to_s.include? due_date_attributes[1]['due_date']
- assert redirect_to(:action => 'edit', :id => new_assignment)
+
+ expected_date = new_assignment.section_due_dates[1].due_date
+ actual_date = Time.parse(due_date_attributes['1']['due_date'])
+ assert_equal expected_date, actual_date
+
+ assert redirect_to(action: 'edit', id: new_assignment)
end
end # With some sections
end #creating new assignment
@@ -249,7 +263,7 @@ def teardown
assert_equal @assignment.submission_rule.type.to_s,
a.submission_rule.type.to_s
assert_not_nil assigns(:assignment)
- assert !assigns(:assignment).errors.empty?
+ refute_empty assigns(:assignment).errors
end
should 'be able to add periods to submission rule class' do
@@ -317,15 +331,18 @@ def teardown
assert @assignment.submission_rule.is_a?(GracePeriodSubmissionRule)
put_as @admin,
- :update,
- {:id => @assignment.id,
- :assignment => {
- :submission_rule_attributes => {
- :type => 'NoLateSubmissionRule',
- :periods_attributes => {
- '1' => { :id => period.id } },
- :id => @assignment.submission_rule.id,
- }}}
+ :update,
+ id: @assignment.id,
+ assignment: {
+ submission_rule_attributes: {
+ type: 'NoLateSubmissionRule',
+ periods_attributes: {
+ '1' => { id: period.id, hours: 1 }
+ },
+ id: @assignment.submission_rule.id,
+ }
+ }
+
assert_response :redirect
# no errors should have been produced
assert_equal [], assigns(:assignment).errors[:base]
@@ -527,8 +544,8 @@ def teardown
@assignment.reload
assert_equal false, @assignment.section_due_dates_type
- assert_equal 0, @assignment.section_due_dates.size
- assert_equal 0, SectionDueDate.all.size
+ assert_empty @assignment.section_due_dates
+ assert_empty SectionDueDate.all
end
end # -- with an assignment
View
7 test/functional/authenticated_controller_test.rb
@@ -4,6 +4,13 @@
# overrides common request types with authentication
class AuthenticatedControllerTest < ActionController::TestCase
+ def user_params(attrs = {})
+ args = { user_name: 'test',
+ first_name: 'Mark',
+ last_name: 'Us' }.merge(attrs)
+ ActionController::Parameters.new(user: args)
+ end
+
# Performs GET request as the supplied user for authentication
def get_as(user, action, params=nil, flash=nil)
session_vars = { 'uid' => user.id, 'timeout' => 3.days.from_now }
View
11 test/functional/students_controller_test.rb
@@ -42,7 +42,7 @@ class StudentsControllerTest < AuthenticatedControllerTest
end
should 'be able to get :new' do
- get_as @admin, :new
+ get_as @admin, :new, user_params
assert_response :success
end
@@ -128,10 +128,11 @@ class StudentsControllerTest < AuthenticatedControllerTest
should 'be able to update student (and change his section)' do
put_as @admin,
:update,
- :id => @student.id,
- :user => {:last_name => 'Doe',
- :first_name => 'John',
- :section_id => @section.id }
+ id: @student.id,
+ user: { user_name: 'machinist_student1',
+ last_name: 'Doe',
+ first_name: 'John',
+ section_id: @section.id }
assert_response :redirect
assert_equal I18n.t('students.update.success',
:user_name => @student.user_name),
Something went wrong with that request. Please try again.