Reputation: 43
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
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
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
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