Daniel Thomas
Daniel Thomas

Reputation: 43

How to protect against tampering with the URL in Rails

My Rails student planner application has a few issues regarding URL tampering. I believe they probably all share a similar solution but I'm having difficulty.

When viewing an assignment (students/:id/assignments/:id), changing the assignment’s ID in the URL to the ID of an assignment belonging to another student sometimes leads to a "no method error" in my assignments#show page, other times it will show the other student's assignment, when ideally I'd like to just redirect back to their home page.

Similarly, this happens with the assignment's edit page (students/:id/assignments/:id/edit), course (students/:id/courses/:id) and course's edit page (students/:id/courses/:id/edit). Sometimes I'll get an "ArgumentError in Assignments#edit" when viewing an assignment's edit page.

I believe these should be able to be remedied in my controllers, so I've included my assignments_controller and courses_controller.

Assignments_controller:

class AssignmentsController < ApplicationController
  before_action :require_logged_in
  before_action :set_student

  def new
    if @student && @student.id == current_student.id
      @assignment = Assignment.new
      @courses = Course.where(student_id: current_student.id)
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users assignments.'
    end
  end

  def create
    @assignment = Assignment.new(assignment_params)
    @assignment.student_id = current_student.id if current_student
    @courses = Course.where(student_id: current_student.id)

    if @assignment.save
      redirect_to student_assignments_path(@student)
    else
      render :new
    end
  end

  def index
    if @student && @student.id == current_student.id
      @assignments = Assignment.where(student_id: current_student.id)
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users assignments.'
    end
  end

  def show
    #student = Student.find_by(id: params[:student_id])
    if @student && @student.id == current_student.id
      #@assignment = student.assignments.find_by(id: params[:id])
      @assignment = Assignment.find_by(id: params[:id])
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users assignments.'
    end
  end

  def edit
    if @student && @student.id == current_student.id
      @assignment = Assignment.find_by(id: params[:id])
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users assignments.'
    end
  end

  def update
    student = Student.find_by(id: params[:student_id])
    @assignment = Assignment.find_by(id: params[:id])
    @assignment.update(params.require(:assignment).permit(:title, :due_date))
    redirect_to student_assignment_path(student, @assignment)
  end

  def destroy
    @student = Student.find_by(id: params[:student_id])
    @assignment = Assignment.find_by(id: params[:id]).destroy
    redirect_to student_path(@student), notice: 'Assignment was successfully completed.'
  end

  private

    def assignment_params
      params.require(:assignment).permit(:title, :due_date, :course_id, :student_id)
    end

    def set_student
      @student = Student.find_by(id: params[:student_id])
    end
end

Courses_controller:

class CoursesController < ApplicationController
  before_action :require_logged_in
  before_action :set_student

  def new
    if @student && @student.id == current_student.id
      @course = Course.new
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users courses.'
    end
  end

  def create
    if @student && @student.id == current_student.id
      @course = Course.create(course_params)
      @course.student_id = params[:student_id]

      if @course.save
        redirect_to student_courses_path(@student)
      else
        render :new
      end
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users courses.'
    end
  end

  def index
    if @student && @student.id == current_student.id
      @courses = Course.where(student_id: current_student.id)
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users courses.'
    end
  end

  def show
    @student = Student.find_by(id: params[:student_id])
    if @student && @student.id == current_student.id
      @course = @student.courses.find_by(id: params[:id])
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users courses.'
    end
  end

  def edit
    if @student && @student.id == current_student.id
      @course = Course.find_by(id: params[:id])
    else
      redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users courses.'
    end
  end

  def update
    student = Student.find_by(id: params[:student_id])
    @course = Course.find_by(id: params[:id])
    @course.update(params.require(:course).permit(:course_name))
    redirect_to student_course_path(student, @course)
  end

  def destroy
    @student = Student.find_by(id: params[:student_id])
    @course = Course.find_by(id: params[:id]).destroy
    redirect_to student_path(@student), notice: 'Course was successfully deleted.'
  end

  private

    def course_params
      params.require(:course).permit(:course_name)
    end

    def set_student
      @student = Student.find_by(id: params[:student_id])
    end
end

Upvotes: 1

Views: 393

Answers (4)

Fernand
Fernand

Reputation: 1333

This might help you:

In your CoursesController and AssignmentsController, add a before_action in your controllers that will limit student's access.

#xxx_controller.rb
class XxxController < ApplicationController
  before_action :require_logged_in
  before_action :set_student
  before_action :check_owner, only: [:show, :edit, :update, :destroy]

Then define the method in your ApplicationController:

#application_controller.rb
def check_owner
  if @student.blank? || @student.id != current_student.id
    redirect_to student_path(current_student), error: 'Sorry, you can\'t view another Users assignments.'
  end
end

Upvotes: 1

tadman
tadman

Reputation: 211740

This line is the source of all the problems:

@assignment = Assignment.find_by(id: params[:id])

That's a huge mistake. I'd argue that you never, ever use the top-level model to fetch records that must be secured. The failure state of this code is a user sees everything. This is a problem that can't be fixed by patching over it with an access-control list. Those may not apply correctly every time, someone could find a loophole.

Instead you do this:

@assignment = @student.assignments.find_by(id: params[:id])

Worst-case scenario is you get a not-found error. It's impossible for someone to bypass this by hacking around with the URL. The failure state here is the record is not found.

If you want your URLs resistant to tampering you'll also want to use non-sequential identifiers. On MySQL it's often best to create a secondary column specifically for this purpose, like called param or slug or ident, whatever you prefer, and populate that with something random and harmless like:

 before_validation :assign_slug

 def assign_slug
   self.slug ||= SecureRandom.uuid
 end

Where that's indexed in your schema for quick retrieval. Where you have a student relationship:

 add_index :assignments, [ :student_id, :slug ]

Postgres allows using UUID primary keys which might be verbose, but don't allow people to tinker and experiment to expose information. You really can't "guess" a randomized UUID value.

Upvotes: 2

Roman Alekseiev
Roman Alekseiev

Reputation: 1920

I guess access restriction is what you are looking for. CanCanCan will help you to set proper access to pages.

Also please replace find_by(id: params[:id]) with find(id) because it is more readable and more efficient.

Upvotes: 0

Tibo
Tibo

Reputation: 61

So you have authentication set up allowing access only to logged in users.

Now you need an authorization process to allow access to certain actions/resources depending on the user id or role.

One popular option is Pundit and the readme page should get you started.

Upvotes: -1

Related Questions