CVE-2015-10033: Incorrect Authorization (CWE-863)
value: A vulnerability, which was classified as problematic, was found in jvvlee MerlinsBoard. This affects an unknown part of the component Grade Handler. The manipulation leads to improper authorization. The identifier of the patch is 134f5481e2914b7f096cd92a22b1e6bcb8e6dfe5. It is recommended to apply a patch to fix this issue. The identifier VDB-217713 was assigned to this vulnerability.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 6.5
Published: 2023-01-09T21:15Z
Repository: https://github.com/jvvlee/MerlinsBoard
Language: JavaScript
Files changed: app/assets/javascripts/collections/grades.js, app/assets/javascripts/models/grade.js, app/assets/javascripts/routers/router.js, app/assets/javascripts/views/courses/course-form.js, app/assets/javascripts/views/courses/course-list.js, app/assets/javascripts/views/courses/course_search.js, app/assets/javascripts/views/courses/courses_enroll.js, app/assets/javascripts/views/grades/grade-search-student.js, app/assets/javascripts/views/grades/grades-student.js, app/assets/templates/courses/coursesearch.jst.ejs, app/assets/templates/courses/list.jst.ejs, app/assets/templates/grades/grades-student-list.jst.ejs, app/controllers/api/grades_controller.rb, app/views/api/grades/index.json.jbuilder, config/routes.rb, db/seeds.rb
Commit date: 2015-03-19 12:34:41 -0400
Commit message: Progresses on grades change view and events. Discovered security vulnerability that allows a course admin to change whatever grade they want if they know the ID, even in other classes

Diff: diff --git a/app/assets/javascripts/collections/grades.js b/app/assets/javascripts/collections/grades.js
index a1f43d8..9f44087 100644
--- a/app/assets/javascripts/collections/grades.js
+++ b/app/assets/javascripts/collections/grades.js
@@ -5,11 +5,11 @@ MerlinsBoard.Collections.Grades = Backbone.Collection.extend({
     this.url = "api/users/" + options["user_id"] + "/grades"
   },
 
   model: MerlinsBoard.Models.Grade,
 
-  getOrFetch: function (id) {
+  getOrFetch: function (id, course_id) {
     var grade = this.get(id);
     var grades = this;
 
     if (!grade) {
       grade = new MerlinsBoard.Models.Grade({id: id});
@@ -29,19 +29,29 @@ MerlinsBoard.Collections.Grades = Backbone.Collection.extend({
       options = {};
     }
 
     //some logic here to check if "data" was already passed in, and fusing that to the data parameter...
 
-    _.extend(options,{ data: $.param({ course_id: this.course_id}) }); //options is changed
-    //with this, I might always have to bind fetch - be mindful of this in case I need to fetch more data
+    _.extend(options,{ data: $.param({ course_id: this.course_id}) });
     return Backbone.Collection.prototype.fetch.call(this, options);
   },
 
+  student: function () {
+    if (!this._student) {
+      this._student = new MerlinsBoard.Models.User();
+    }
+
+    return this._student
+  },
+
   parse: function (resp) {
-    this.student = new MerlinsBoard.Models.User({fname: resp.student_fname,lname: resp.student_lname});
+    this.student().set({fname: resp.student_fname,lname: resp.student_lname});
+    // this.course_id = resp.course_id should only set once and then never again
 
     resp.student_fname.delete //is there a better way to clean this up?
     resp.student_fname.delete
+    resp.course_id.delete
+
     return resp.grades
   }
 
 })
diff --git a/app/assets/javascripts/models/grade.js b/app/assets/javascripts/models/grade.js
index 8799601..75257cd 100644
--- a/app/assets/javascripts/models/grade.js
+++ b/app/assets/javascripts/models/grade.js
@@ -1,4 +1,4 @@
 MerlinsBoard.Models.Grade = Backbone.Model.extend({
   urlRoot: 'api/grades',
   validate: function () {}
-})
\ No newline at end of file
+})
diff --git a/app/assets/javascripts/routers/router.js b/app/assets/javascripts/routers/router.js
index 36e67ff..4ea5540 100644
--- a/app/assets/javascripts/routers/router.js
+++ b/app/assets/javascripts/routers/router.js
@@ -30,24 +30,24 @@ MerlinsBoard.Routers.Router = Backbone.Router.extend({
     "course/:id/assignments/new" : "newAssignment",
     "course/:id/assignments" : "showAssignments",
     "course/:course_id/assignments/:id/edit" : "editAssignment",
     //grades
     "course/:id/grades/student-search" : "gradeSearch",
-    "course/:course_id/grades/user/:user_id" : "gradesShow"
+    "course/:course_id/grades/user/:user_id" : "gradeShow"
     //misc
 //     "user/:id": "showuser"
     //":wildcard": "does not exist" --self explanatory
 	},
 
 
   //course resources
 	enrollcourses: function () {
     var allcourses = new MerlinsBoard.Collections.Courses([],{owner: this.currentUser});
 
-    allcourses.fetch();
+    this.currentUser.fetch()
 
-    var enrollView = new MerlinsBoard.Views.CoursesEnroll({collection: allcourses,model: this.currentUser});
+    var enrollView = new MerlinsBoard.Views.CoursesEnroll({model: this.currentUser});
     this.swapView(enrollView);
   },
 
 	showuser: function () {
     var userView = new MerlinsBoard.Views.UserShow({model: this.currentUser});
@@ -152,10 +152,11 @@ MerlinsBoard.Routers.Router = Backbone.Router.extend({
   },
 
   gradeShow: function (course_id, user_id) {
     // var course = MerlinsBoard.Courses.getOrFetch(id);
     var grades = new MerlinsBoard.Collections.Grades({course_id: course_id, user_id: user_id});
+
     grades.fetch();
 
     var gradesList = new MerlinsBoard.Views.GradesStudent({collection: grades});
     this.swapView(gradesList)
   },
diff --git a/app/assets/javascripts/views/courses/course-form.js b/app/assets/javascripts/views/courses/course-form.js
index f15dc5d..9b33d8e 100644
--- a/app/assets/javascripts/views/courses/course-form.js
+++ b/app/assets/javascripts/views/courses/course-form.js
@@ -21,11 +21,11 @@ MerlinsBoard.Views.CourseForm = Backbone.View.extend({
 		var attrs = $(event.target).serializeJSON();
     debugger
 		this.model.save(attrs, {
 			success: function () {
 				MerlinsBoard.Courses.add(this.model,{merge: true})
-				Backbone.history.navigate("",{trigger: true}) //instead do a "course created/saved"
+				Backbone.history.navigate("course/search",{trigger: true}) 
 			}.bind(this),
 			error: function (model,resp) {
 				var errorArray = resp.responseJSON;
         var $errorList = $("<ul>").addClass("errors");
         _.each(errorArray, function (error) {
diff --git a/app/assets/javascripts/views/courses/course-list.js b/app/assets/javascripts/views/courses/course-list.js
index 073036c..8cb4e0b 100644
--- a/app/assets/javascripts/views/courses/course-list.js
+++ b/app/assets/javascripts/views/courses/course-list.js
@@ -1,11 +1,10 @@
 MerlinsBoard.Views.CoursesList = Backbone.View.extend({
 	initialize: function () {
- 		this.listenTo(this.collection, "add remove sync", this.render);
+ 		this.listenTo(this.collection, "reset add sync", this.render);
 	},
 
-
 	template: JST["courses/list"],
 
 	tagName: "ul",
 
 	className: "course-list",
diff --git a/app/assets/javascripts/views/courses/course_search.js b/app/assets/javascripts/views/courses/course_search.js
index 91f6c96..decad35 100644
--- a/app/assets/javascripts/views/courses/course_search.js
+++ b/app/assets/javascripts/views/courses/course_search.js
@@ -1,48 +1,39 @@
 MerlinsBoard.Views.CoursesSearch = Backbone.View.extend({
 	initialize: function () {
-		this.searchCollection = new MerlinsBoard.Collections.CoursesSearch();
 	},
 
 	template: JST["courses/coursesearch"],
 
 	render: function () {
 		var renderedContent = this.template({courses: this.collection});
 		this.$el.html(renderedContent);
 		return this
 	},
 
+	searchCollection: function () {
+		if (!this._searchCollection) {
+			this._searchCollection = new MerlinsBoard.Collections.CoursesSearch();
+		}
+
+		return this._searchCollection
+	},
+
   tagName: "section",
 
   className: "course-search",
 
 	events: {
 		"submit form.course-find":"search"
 	},
 
-	// search: function (event) {
-	// 	event.preventDefault();
-  //  var query = $("input.course-find-input").val();
-  //
-  //   var filtered = this.collection.filter(function (course) {
-  //     var pattern = new RegExp(query, "gi");
-  //     var result = course.get("name").match(pattern);
-  //     return result
-  //   })
-  //
-  //   var filteredCollection = new MerlinsBoard.Collections.Courses([],{owner: MerlinsBoard.CurrentUser});
-  //   filteredCollection.set(filtered);
-  //
-  //   var searchList = new MerlinsBoard.Views.CoursesList({collection: filteredCollection});
-	// 	this.$('section.course-results').html(searchList.render().$el);
-	// }
-
 	search: function (event) {
 		event.preventDefault();
 	  var queryCourse = $("input.course-find-input").val();
-		this.searchCollection.fetch({data: $.param(query: queryCourse)});
+		this.searchCollection().fetch({data: $.param({query: queryCourse})});
 
-		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection});
-		$('section.course-results').html(searchList.render.$el); //needs to be global from DOM.
+		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection()});
+		//want to call remove on search results
+		$('section.course-results').html(searchList.render().$el);
 	}
 
 })
diff --git a/app/assets/javascripts/views/courses/courses_enroll.js b/app/assets/javascripts/views/courses/courses_enroll.js
index 0b47a35..3f0339a 100644
--- a/app/assets/javascripts/views/courses/courses_enroll.js
+++ b/app/assets/javascripts/views/courses/courses_enroll.js
@@ -1,32 +1,20 @@
 MerlinsBoard.Views.CoursesEnroll = Backbone.View.extend({
 	initialize: function () {
-		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch({collection: this.collection}); //render will put these in manually
+		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch();
 		this.usercoursesView = new MerlinsBoard.Views.CoursesList({collection: this.model.courses()});
 		this.usertaughtcoursesView = new MerlinsBoard.Views.CoursesList({collection: this.model.taughtcourses()});
 	},
-	
+
   template: JST['courses/enroll'],
-	
+
 	render: function () {
 		this.$el.html(this.template());
-    
+
     this.$("section.courses-attended").html(this.usercoursesView.render().$el);
     this.$("section.courses-taught").html(this.usertaughtcoursesView.render().$el);
     this.$("section.course-search").html(this.coursesearchView.render().$el);
-		
-    return this
-	},
-  
-  //below two, again - hardcode URLs instead
-																												
-	show: function (event) {
-		event.preventDefault();
-		var id = $(event.currentTarget).data("id");
-		Backbone.history.navigate("course/" + id + "/enroll", {trigger:true})
-	},
 
-	events: {
-		"click a": "show"
+    return this
 	}
 
 });
diff --git a/app/assets/javascripts/views/grades/grade-search-student.js b/app/assets/javascripts/views/grades/grade-search-student.js
index e77d2a3..a053d44 100644
--- a/app/assets/javascripts/views/grades/grade-search-student.js
+++ b/app/assets/javascripts/views/grades/grade-search-student.js
@@ -1,11 +1,11 @@
-MerlinsBoard.Views.SearchStudent = Backbone.View.extend({
+MerlinsBoard.Views.SearchStudentGradesResults = Backbone.View.extend({
   initialize: function () {
     this.listenTo(this.collection, "add remove reset", this.render)
   },
 
-  className: "grades-studentsearch",
+  className: "grades-student-search",
 
   template: JST["grades/grades-student-search"],
 
   render: function () {
     var renderedContent = this.template({students: this.collection});
diff --git a/app/assets/javascripts/views/grades/grades-student.js b/app/assets/javascripts/views/grades/grades-student.js
index 5c3d7d8..e6848c2 100644
--- a/app/assets/javascripts/views/grades/grades-student.js
+++ b/app/assets/javascripts/views/grades/grades-student.js
@@ -1,14 +1,14 @@
 //Lists a students grade - an admin has access to this view to edit grades
 MerlinsBoard.Views.GradesStudent = Backbone.View.extend({
   initialize: function () {
-    this.listenTo(this.collection, "add change:grade remove", this.render)
+    this.listenTo(this.collection, "add change:grade remove sync", this.render)
     _.bindAll(this, "gradeSaveCallback", "gradeSaveErrorCallback")
-    //for jbuidler - nest each of a student's grade under them along with basic information about the assignment
+    //for jbuidler - nest each of a student's grade under them along with basic information about the assignmen
   },
 
-  template: JST["grades/grades-student"],
+  template: JST["grades/grades-student-list"],
 
   events: {
     "click .grade-number":"editGrade",
     "blur .grade-input": "saveGrade"
   },
@@ -16,45 +16,43 @@ MerlinsBoard.Views.GradesStudent = Backbone.View.extend({
   className: "grade-list",
 
   tagName: "section",
 
   render: function () {
-    var renderedContent = this.template({grades: this.collection});
+    var renderedContent = this.template({grades: this.collection, student: this.collection.student()});
     this.$el.html(renderedContent);
-    return this.$el
+    return this
   },
 
   editGrade: function (event) {
-    var gradeString = $(event.currentTarget).text();
+    var gradeString = $(event.currentTarget).val();
     var num = parseInt(gradeString);
-    var $input = $("<input type='number'>").addClass('grade-input').val(num);
-
+    var $input = $("<input type='number' min='0' step='1' max='100'>").addClass('grade-input').val(num);
     this.modelNumber = $(event.currentTarget).data('id');
-
-    $(".grade-number").html(input)
+    $(".grade-number[data-id=".concat(this.modelNumber,"]")).html($input)
   },
 
   saveGrade: function (event) {
     var editedGrade = this.collection.getOrFetch(this.modelNumber);
-    var newGrade = $('input.grade-input').val();
+    var newGrade = parseInt($('input.grade-input').val());
     var courseID = this.collection.course_id;
 
+    debugger
+
     editedGrade.set({grade: newGrade});
-    //two options, send in the params with the model and strong params takes care of it
-    //or send it in as an option for the save option
-    editedGrade.save({},{success: this.gradeSaveCallback,
-    error: this.gradeSaveErrorCallback,
-    data: $.param({course_id: courseID})
+    editedGrade.save({course_id: courseID},{success: this.gradeSaveCallback(editedGrade),
+    error: this.gradeSaveErrorCallback
     });
   },
 
-  gradeSaveCallback: function () {
-    this.collection.add(editedGrade,{merge: true})//this should be a closure - also editedGrade I think should persist as a variable...
-    // $(".grade-number").html(editedGrade.get('grade')); this wont work because I'm inspecific, but I may not need it anyway to rerender
+  gradeSaveCallback: function (editedGrade) {
+    this.collection.fetch(); //unideal - needs to be banished with composite view paradigm.
+    // this.collection.add(editedGrade,{merge: true});
   },
 
-  gradeSaveErrorCallback: function (model, response) {
+  gradeSaveErrorCallback: function (model, resp) {
+
     var errorArray = resp.responseJSON
     var $errorList = $("<ul>").addClass('errors');
     _.each(errorArray, function (error) {
       var $error = $("<li>").text(error).addClass('error');
       $errorList.append($error);
diff --git a/app/assets/templates/courses/coursesearch.jst.ejs b/app/assets/templates/courses/coursesearch.jst.ejs
index dd0df41..fe25541 100644
--- a/app/assets/templates/courses/coursesearch.jst.ejs
+++ b/app/assets/templates/courses/coursesearch.jst.ejs
@@ -8,7 +8,6 @@ all this will be is organized links-->
 	</label>
 	<input type="submit" value="Search">
 </form>
 
 <section class="course-results">
-
 </section>
diff --git a/app/assets/templates/courses/list.jst.ejs b/app/assets/templates/courses/list.jst.ejs
index d57e598..841fa29 100644
--- a/app/assets/templates/courses/list.jst.ejs
+++ b/app/assets/templates/courses/list.jst.ejs
@@ -1,3 +1,3 @@
 <% courses.each( function (course) { %>
-	<li><a href="#" data-id="<%=course.id%>"><%= course.escape("name") %></a></li>
-<% })%>
\ No newline at end of file
+	<li><a href="<%= '#course/' + course.id + '/enroll'%>"><%= course.escape("name") %></a></li>
+<% })%>
diff --git a/app/assets/templates/grades/grades-student-list.jst.ejs b/app/assets/templates/grades/grades-student-list.jst.ejs
index d8fbbeb..4afe263 100644
--- a/app/assets/templates/grades/grades-student-list.jst.ejs
+++ b/app/assets/templates/grades/grades-student-list.jst.ejs
@@ -1,12 +1,10 @@
-<h1>Grades for <%=grades.escape('fname') + " " + grades.escape('lname')%></h1>
+<h1>Grades for <%= student.escape('fname') + " " + student.escape('lname')%></h1>
 
 <section class="grade-errors"></section>
 
 <% grades.each(function (grade) { %>
-  <h4> grade.escape('assignment_title') </h4>
-  <p> grade.escape('description')</p>
+  <h4> Assignment name:<%= grade.escape('title') %></h4>
+  <p> description: <%= grade.escape('description') %></p>
   Grade: <strong class="grade-number" data-id="<%= grade.id %>"><%= grade.get('grade')%></strong>
   <br>
 <% }) %>
-
-//I think all I'll do is just pretend like nothing happened if the entry is invalid
diff --git a/app/controllers/api/grades_controller.rb b/app/controllers/api/grades_controller.rb
index e6fd7ad..4139e37 100644
--- a/app/controllers/api/grades_controller.rb
+++ b/app/controllers/api/grades_controller.rb
@@ -1,36 +1,44 @@
 class Api::GradesController < Api::ApiController
-  before_action(except: [:index]) {admins_only(params["course_id"])} #wil always need to pass this in on every fetch
+  before_action(only: [:update, :show]) {admins_only(params["course_id"])}
   before_action :is_user_or_instructor?, only: [:index]
 
   def index
     #I may want an internal control here instead of using the before_action...
     @grades = Grade.includes(:assignment,:course,:user).where("user_id = ?", params["user_id"])
     @student = @grades.first.user
+    @course_id = params["course_id"].to_i
+
     @grades = @grades.select {|grade| grade.course.id == params["course_id"].to_i}
   end
 
-  def destroy
-    @grade = Grade.find(params[:id])
-    @grade.destroy
-    render json: {}
-  end
+  #Neither of these may be needed because they should only be created/destroyed depending on the assignment
 
+  # def destroy
+  #   @grade = Grade.find(params[:id])
+  #   @grade.destroy
+  #   render json: {}
+  # end
   # def create
   #   @grade = Grade.new(grade_params)
   #
   #   if @grade.save
   #     render json: @grade
   #   else
   #     render json: @grade.errors.full_messages, status: 422
   #   end
   # end
 
+  def show
+    @grade = Grade.find(params[:id])
+    render json: @grade
+  end
+  #will need to create unique validator to ensure congruency between course auth and resource id course....
   def update
     @grade = Grade.find(params[:id])
 
-    if @grade.save
+    if @grade.update(grade_params)
       render json: @grades
     else
       render json: @grade.errors.full_messages, status: 422
     end
   end
@@ -41,9 +49,9 @@ class Api::GradesController < Api::ApiController
     return if current_user.id == params["user_id"].to_i
     admins_only(params["course_id"])
   end
 
   def grade_params
-    params.require(:grade).permit(:grade, :assignment_id, :user_id)
+    params.permit(:grade, :assignment_id, :user_id) #need to change grade column - it confuses params_wrapper
   end
-  
+
 end
diff --git a/app/views/api/grades/index.json.jbuilder b/app/views/api/grades/index.json.jbuilder
index 9d5b6d7..07f47a3 100644
--- a/app/views/api/grades/index.json.jbuilder
+++ b/app/views/api/grades/index.json.jbuilder
@@ -1,16 +1,17 @@
 json.grades @grades do |grade_obj|
+  json.id grade_obj.id
   json.grade grade_obj.grade
-  json.assignment_id grade_obj.assignment_id
+  json.id grade_obj.assignment_id
   json.user_id grade_obj.user_id
-  json.assignment_title grade_obj.assignment.title
-  json.assignment_description grade_obj.assignment.description
+  json.title grade_obj.assignment.title
+  json.description grade_obj.assignment.description
 end
 
 json.student_fname @student.fname
 json.student_lname @student.lname
-json.course_id @student.course.id
+json.course_id @course_id
 
 #remember then that for a single model, only top-level attrs will be assigned
 # for a collection, each entry in the array should be top-level attrs (or wrapped in only a single object wrapper)
 # but the array itself must be top-level
-# weird: Cannot mix json.array! with other top-level attrs
\ No newline at end of file
+# weird: Cannot mix json.array! with other top-level attrs
diff --git a/config/routes.rb b/config/routes.rb
index eb4fe99..4161065 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -13,10 +13,11 @@ Rails.application.routes.draw do
     resources :coursesinstructors, only: [:create, :destroy]
     resources :coursesstudents, only: [:create, :destroy], controller: "courses_students"
     resources :announcements #might have to "member do" for easy-access custom routes from a particular course
     resources :assignments
     resources :resources
+    resources :grades, only: [:update, :show]
     resources :courses do
       get "course_search", on: :collection
     end
     resources :users, only: [:show, :index] do
       get "users_search", on: :collection
diff --git a/db/seeds.rb b/db/seeds.rb
index 408de1e..2046b78 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -7,10 +7,12 @@
 #   Mayor.create(name: 'Emanuel', city: cities.first)
 
 #should create master user that has most functionality available to it.
 
 User.create(fname:"Jonathan", lname: "Lee", email: "l33.jonathan@gmail.com", password: "testing")
+User.create(fname:"John", lname: "Doe", email: "johndoe@gmail.com",
+password: "testing")
 
 (1..10).each do |user_no|
   User.create(
     fname: Faker::Name.first_name,
     lname: Faker::Name.last_name,

Vulnerable code:   getOrFetch: function (id) {
    _.extend(options,{ data: $.param({ course_id: this.course_id}) }); //options is changed
    //with this, I might always have to bind fetch - be mindful of this in case I need to fetch more data
    this.student = new MerlinsBoard.Models.User({fname: resp.student_fname,lname: resp.student_lname});
})
    "course/:course_id/grades/user/:user_id" : "gradesShow"
    allcourses.fetch();
    var enrollView = new MerlinsBoard.Views.CoursesEnroll({collection: allcourses,model: this.currentUser});
				Backbone.history.navigate("",{trigger: true}) //instead do a "course created/saved"
 		this.listenTo(this.collection, "add remove sync", this.render);

		this.searchCollection = new MerlinsBoard.Collections.CoursesSearch();
	// search: function (event) {
	// 	event.preventDefault();
  //  var query = $("input.course-find-input").val();
  //
  //   var filtered = this.collection.filter(function (course) {
  //     var pattern = new RegExp(query, "gi");
  //     var result = course.get("name").match(pattern);
  //     return result
  //   })
  //
  //   var filteredCollection = new MerlinsBoard.Collections.Courses([],{owner: MerlinsBoard.CurrentUser});
  //   filteredCollection.set(filtered);
  //
  //   var searchList = new MerlinsBoard.Views.CoursesList({collection: filteredCollection});
	// 	this.$('section.course-results').html(searchList.render().$el);
	// }

		this.searchCollection.fetch({data: $.param(query: queryCourse)});
		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection});
		$('section.course-results').html(searchList.render.$el); //needs to be global from DOM.
		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch({collection: this.collection}); //render will put these in manually
	
	
    
		
    return this
	},
  
  //below two, again - hardcode URLs instead
																												
	show: function (event) {
		event.preventDefault();
		var id = $(event.currentTarget).data("id");
		Backbone.history.navigate("course/" + id + "/enroll", {trigger:true})
	},
	events: {
		"click a": "show"
MerlinsBoard.Views.SearchStudent = Backbone.View.extend({
  className: "grades-studentsearch",
    this.listenTo(this.collection, "add change:grade remove", this.render)
    //for jbuidler - nest each of a student's grade under them along with basic information about the assignment
  template: JST["grades/grades-student"],
    var renderedContent = this.template({grades: this.collection});
    return this.$el
    var gradeString = $(event.currentTarget).text();
    var $input = $("<input type='number'>").addClass('grade-input').val(num);


    $(".grade-number").html(input)
    var newGrade = $('input.grade-input').val();
    //two options, send in the params with the model and strong params takes care of it
    //or send it in as an option for the save option
    editedGrade.save({},{success: this.gradeSaveCallback,
    error: this.gradeSaveErrorCallback,
    data: $.param({course_id: courseID})
  gradeSaveCallback: function () {
    this.collection.add(editedGrade,{merge: true})//this should be a closure - also editedGrade I think should persist as a variable...
    // $(".grade-number").html(editedGrade.get('grade')); this wont work because I'm inspecific, but I may not need it anyway to rerender
  gradeSaveErrorCallback: function (model, response) {

	<li><a href="#" data-id="<%=course.id%>"><%= course.escape("name") %></a></li>
<% })%>
<h1>Grades for <%=grades.escape('fname') + " " + grades.escape('lname')%></h1>
  <h4> grade.escape('assignment_title') </h4>
  <p> grade.escape('description')</p>

//I think all I'll do is just pretend like nothing happened if the entry is invalid
  before_action(except: [:index]) {admins_only(params["course_id"])} #wil always need to pass this in on every fetch
  def destroy
    @grade = Grade.find(params[:id])
    @grade.destroy
    render json: {}
  end
    if @grade.save
    params.require(:grade).permit(:grade, :assignment_id, :user_id)
  
  json.assignment_id grade_obj.assignment_id
  json.assignment_title grade_obj.assignment.title
  json.assignment_description grade_obj.assignment.description
json.course_id @student.course.id
# weird: Cannot mix json.array! with other top-level attrs
Fixed code:   getOrFetch: function (id, course_id) {
    _.extend(options,{ data: $.param({ course_id: this.course_id}) });
  student: function () {
    if (!this._student) {
      this._student = new MerlinsBoard.Models.User();
    }

    return this._student
  },

    this.student().set({fname: resp.student_fname,lname: resp.student_lname});
    // this.course_id = resp.course_id should only set once and then never again
    resp.course_id.delete

})
    "course/:course_id/grades/user/:user_id" : "gradeShow"
    this.currentUser.fetch()
    var enrollView = new MerlinsBoard.Views.CoursesEnroll({model: this.currentUser});

				Backbone.history.navigate("course/search",{trigger: true}) 
 		this.listenTo(this.collection, "reset add sync", this.render);
	searchCollection: function () {
		if (!this._searchCollection) {
			this._searchCollection = new MerlinsBoard.Collections.CoursesSearch();
		}

		return this._searchCollection
	},

		this.searchCollection().fetch({data: $.param({query: queryCourse})});
		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection()});
		//want to call remove on search results
		$('section.course-results').html(searchList.render().$el);
		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch();



    return this
MerlinsBoard.Views.SearchStudentGradesResults = Backbone.View.extend({
  className: "grades-student-search",
    this.listenTo(this.collection, "add change:grade remove sync", this.render)
    //for jbuidler - nest each of a student's grade under them along with basic information about the assignmen
  template: JST["grades/grades-student-list"],
    var renderedContent = this.template({grades: this.collection, student: this.collection.student()});
    return this
    var gradeString = $(event.currentTarget).val();
    var $input = $("<input type='number' min='0' step='1' max='100'>").addClass('grade-input').val(num);
    $(".grade-number[data-id=".concat(this.modelNumber,"]")).html($input)
    var newGrade = parseInt($('input.grade-input').val());
    debugger

    editedGrade.save({course_id: courseID},{success: this.gradeSaveCallback(editedGrade),
    error: this.gradeSaveErrorCallback
  gradeSaveCallback: function (editedGrade) {
    this.collection.fetch(); //unideal - needs to be banished with composite view paradigm.
    // this.collection.add(editedGrade,{merge: true});
  gradeSaveErrorCallback: function (model, resp) {

	<li><a href="<%= '#course/' + course.id + '/enroll'%>"><%= course.escape("name") %></a></li>
<% })%>
<h1>Grades for <%= student.escape('fname') + " " + student.escape('lname')%></h1>
  <h4> Assignment name:<%= grade.escape('title') %></h4>
  <p> description: <%= grade.escape('description') %></p>
  before_action(only: [:update, :show]) {admins_only(params["course_id"])}
    @course_id = params["course_id"].to_i

  #Neither of these may be needed because they should only be created/destroyed depending on the assignment
  # def destroy
  #   @grade = Grade.find(params[:id])
  #   @grade.destroy
  #   render json: {}
  # end
  def show
    @grade = Grade.find(params[:id])
    render json: @grade
  end
  #will need to create unique validator to ensure congruency between course auth and resource id course....
    if @grade.update(grade_params)
    params.permit(:grade, :assignment_id, :user_id) #need to change grade column - it confuses params_wrapper

  json.id grade_obj.id
  json.id grade_obj.assignment_id
  json.title grade_obj.assignment.title
  json.description grade_obj.assignment.description
json.course_id @course_id
# weird: Cannot mix json.array! with other top-level attrs
    resources :grades, only: [:update, :show]
User.create(fname:"John", lname: "Doe", email: "johndoe@gmail.com",
password: "testing")