CSC/ECE 517 Fall 2019 - E1991. Improvements to anonymized view

From Expertiza_Wiki
Jump to navigation Jump to search

Introduction

The Expertiza project takes advantage of peer-review among students to allow them to learn from each other. Tracking the time that a student spends on each submitted resources is meaningful to instructors to study and improve the teaching experience. Unfortunately, most peer assessment systems do not manage the content of students’ submission within the systems. They usually allow the authors to submit external links to the submission (e.g. GitHub code / deployed application), which makes it difficult for the system to track the time that the reviewers spend on the submissions.


Problem Statement

The main idea of this project is to improve anonymized view in Expertiza. The feature is already implemented. However, there are issues / limitations with the current implementation which we will try to improve.

Anonymized View For Students

Currently, anonymized view is not implemented for students. Although, not directly required by students, instructors may want to use anonymized view when impersonating a student.

Issue with Impersonating Anonymized Student

In an anonymized view, instructors cannot impersonate a student because the username field has the anonymized name instead of their real name in the database. In order to be able to impersonate a student, we need to know their real names from database.

Wrong Anonymized Names

In some places, anonymized names are not shown. For example, heatgrid view. We need to fix occurrences in different parts of the application where anonymized names are not shown.

Anonymizing Team Names

Currently, team names are not anonymized. We need to extend the same logic of anonymization to teams.

Optional Objective

Use randomized American names for anonymized users.

Proposed Solution

In this section, we propose solutions to the problems / issues discussed above.

Anonymized View For Students

Instructors often use 'impersonate' functionality to use Expertiza as a different user. Sometimes they also need to use anonymized view while they are impersonating a student. Therefore, anonymized view needs to be extended to students as well.

In order to extend Anonymized view for students, the first thing we need to do is extend the following function :

 def set_anonymized_view
   anonymized_view_starter_ips = $redis.get('anonymized_view_starter_ips') || 
   session[:ip] = request.remote_ip
   if anonymized_view_starter_ips.include? session[:ip]
     anonymized_view_starter_ips.delete!(" #{session[:ip]}")
   else
     anonymized_view_starter_ips += " #{session[:ip]}"
   end
   $redis.set('anonymized_view_starter_ips', anonymized_view_starter_ips)
   redirect_to :back
 end

This function is responsible to switch back and forth between normal view and an anonymized view. We could figure out that the session of current user is being manipulated to change views. The main driving logic works by storing user's IP address in a value field of anonymized_view_starter_ips which is in Redis database.

Refer the following flow chart for design details:

Impersonating Student Using Anonymized Names

When an instructor is already in Anonymized view and wants to impersonate a student, they have to quit the anonymized view and get the real username of that student. However, we want the instructor to be able to impersonate a student by using their anonymized names. The following function in impersonate_controller.rb is responsible to impersonate students.

 def impersonate
   ...
   begin
     original_user = session[:super_user] || session[:user]
     # Impersonate using form on /impersonate/start
     if params[:impersonate].nil?
       ...
       user = User.find_by(name: params[:user][:name]) 
       ...
     else
       # Impersonate a new account
       if !params[:impersonate][:name].empty?
         ...
         user = User.find_by(name: params[:impersonate][:name])          
         ...
       else
         ...
       end
     end
     ...
   rescue Exception => e
     flash[:error] = e.message
     redirect_to :back
   end
 end

user.rb This file is responsible to convert anonymized name to original name

 def self.real_user_from_anonymized_name(anonymized_name)
   user_id = anonymized_name.split(' ')[1]
   user = User.find_by(id: user_id)
   return user
 end

impersonate_controller.rb this code is responsible to change original user name to anonymized user name

      if User.anonymized_view?(session[:ip])
        # get real name when instructor is in anonymized view
        user = User.real_user_from_anonymized_name(params[:user][:name])
      else         
        user = User.find_by(name: params[:user][:name])
      end

In the above snippet, we have omitted the irrelevant parts of code. The only change we need to do is the logic for User.find_by() function. We will add a condition to check whether the current view is anonymized or not. If anonymized mode is set, we will convert the anonymous name back to the original name of the student. We will then make 'find' query using the real name of the student instead of using their anonymized name.

The procedure to do this would be opposite of what we do to get anonymized names in first place. See following code snippet :

 def name(ip_address = nil)
   User.anonymized_view?(ip_address) ? self.role.name + ' ' + self.id.to_s : self[:name]
 end

The above snippet is an example of how anonymized names are generated.

Please refer the following flowchart for design details;

Anonymizing Team Names

Right now, team names are not anonymized. We need to add accessor methods for team names in team.rb model file.

Additionally, we need a function to check whether Anonymized view is set or not. In this function, we will generate anonymized name whenever Anonymized view is set.

team.rb This code is responsible to generate anonymized name for team when user is in anonymized mode

 def name(ip_address = nil)
   if User.anonymized_view?(ip_address)
     team_name = "Anonymized_Team_#{self[:id]}"
   else
     self[:name]
   end
 end

In the above code change we have overridden the getter method for name attribute and have implemented customized method to get name of team. Decorator pattern is used to get the name of team where if the user is in anonymised mode then name is modified otherwise original name is returned.

Wrong Anonymized Names

There are places in the application where Anonymized names are not shown even when mode is set.

Here are some examples where anonymized names are not shown :

  1. HeatGrid View : On 'view grades' page, there are different tabs where detailed scores, reviews, etc are displayed in a table. The table's column title use real names of the students even when Anonymized mode is set.
  1. Grade Reports : Similarly, on grades page, we can see overall reports of student grades. When in Anonymized mode, the student names are not anonymized in reports.

This errors are fixed by above code changes. Code change in user.rb and impersonate_controller.rb fix the issue regarding anonymized user name and team.rb fix the issue regarding the anonymized team name.

Test Plan

In this section, we discuss what the expected output of all the features will be in terms of well defined test steps.

Test for switching to anonymize mode for student

Following cases are covered:

  • Instructor should be able to impersonate a user with their real name
  • Instructor should be able to impersonate a user with their anonymized name
  • Instructor should be able to impersonate a user while already impersonating a user

impersonate_controller_spec.rb this code is responsible to test the functionality to switch to anonymized view for student

describe ImpersonateController do

   let(:instructor) { build(:instructor, id: 2) }
   let(:student1) { build(:student, id: 30, name: :Amanda)}
   let(:student2) { build(:student, id: 40, name: :Brian)}
   # impersonate is mostly used by instructors 
   # run all tests using instructor account
   # except some exceptions where we'll use other accounts
   before(:each) do
       stub_current_user(instructor, instructor.role.name, instructor.role)
   end
   context "#impersonate" do
       it 'when instructor tries to impersonate another user' do 
           expect(controller.action_allowed?).to be true
       end
       it 'when student tries to impersonate another user' do
           stub_current_user(student1, student1.role.name, student1.role)
           expect(controller.action_allowed?).to be false
       end
       it 'redirects to back' do
           allow(User).to receive(:find_by).with(name: student1.name).and_return(student1)
           request.env["HTTP_REFERER"] = "http://www.example.com"
           @params = { user: { name: student1.name } }
           get :impersonate, @params
           expect(response).to redirect_to("http://www.example.com")
       end
       it 'instructor should be able to impersonate a user with their real name' do
           allow(User).to receive(:find_by).with(name: student1.name).and_return(student1)
           allow(instructor).to receive(:can_impersonate?).with(student1).and_return(true)
           request.env["HTTP_REFERER"] = "http://www.example.com"
           @params = { user: { name: student1.name } }
           @session = { user: instructor }
           post :impersonate, @params, @session
           expect(session[:super_user]).to eq instructor
           expect(session[:user]).to eq student1
           expect(session[:original_user]).to eq instructor
           expect(session[:impersonate]).to be true
       end
       it 'instructor redirects to student home page after impersonating a student' do
           allow(User).to receive(:find_by).with(name: student1.name).and_return(student1)
           allow(instructor).to receive(:can_impersonate?).with(student1).and_return(true)
           request.env["HTTP_REFERER"] = "http://www.example.com"
           @params = { user: { name: student1.name } }
           @session = { user: instructor }
           post :impersonate, @params, @session
           expect(response).to redirect_to("/tree_display/drill")
       end
   end

end

Anonymized View For Students

  1. Login as an instructor
  2. Impersonate a student
  3. Switch to Anonymized view as a student

Edge cases :

  • Instructor impersonates Student1, switches to Anonymized view, impersonates another student Student2, the application still should be in Anonymized view.
  • Instructor switches the application into Anonymized view, impersonates a student Student1, impersonates another student Student2, returns back to their own account by disabling impersonate view, the app still should be in Anonymized view.

users_controller_specs.rbis responsible to test functionality for student.

   # check whether student / instructor is allowed to set anonymized view
   it 'allows student / instructor to set anonymized view' do
     params = {action: 'set_anonymized_view'}
     allow(controller).to receive(:params).and_return(params)
     expect(controller.action_allowed?).to be true
     stub_current_user(student7, student7.role.name, student7.role)
     allow(controller).to receive(:params).and_return(params)
     expect(controller.action_allowed?).to be true
   end
   # check there are no errors while setting anonymized view as a student
   it 'redirects to back' do
     stub_current_user(student7, student7.role.name, student7.role)
     request.env["HTTP_REFERER"] = "http://www.example.com"
     @params = {}
     session = {user: student7}
     get :set_anonymized_view, params: @params, session: session
     expect(response).to redirect_to("http://www.example.com")
   end


Impersonating Student Using Anonymized Names

  1. Login as an instructor
  2. Switch to Anonymized view
  3. Find a student from list of users
  4. Assert that the student name is anonymized
  5. Use the anonymized name to impersonate the real user behind anonymous entity

Edge cases :

  • Impersonating Anonymized user, the application should preserve its Anonymized state even after impersonating the student.


impersonate_controller_spec.rb this code is responsible to test the functionality to switch to anonymized view for student using anonymized name

       it 'instructor should be able to impersonate a user with their anonymized name' do
           allow(User).to receive(:find_by).with(name: student1.name).and_return(student1)
           allow(instructor).to receive(:can_impersonate?).with(student1).and_return(true)
           allow(User).to receive(:anonymized_view?).and_return(true)
           allow(User).to receive(:real_user_from_anonymized_name).with("Student 30").and_return(student1)
           request.env["HTTP_REFERER"] = "http://www.example.com"
           @params = { user: { name: "Student 30" } }
           @session = { user: instructor }
           post :impersonate, @params, @session
           expect(session[:super_user]).to eq instructor
           expect(session[:user]).to eq student1
           expect(session[:original_user]).to eq instructor
           expect(session[:impersonate]).to be true
       end
       it 'instructor should be able to impersonate a user while already impersonating a user' do
           allow(User).to receive(:find_by).with(name: student1.name).and_return(student1)
           allow(User).to receive(:find_by).with(name: student2.name).and_return(student2)
           allow(instructor).to receive(:can_impersonate?).with(student1).and_return(true)
           allow(instructor).to receive(:can_impersonate?).with(student2).and_return(true)
           request.env["HTTP_REFERER"] = "http://www.example.com"
           @params = { user: { name: student1.name } }
           @session = { user: instructor }
           post :impersonate, @params, @session
           @params = { user: { name: student2.name } }
           post :impersonate, @params, @session
           expect(session[:super_user]).to eq instructor
           expect(session[:user]).to eq student2
           expect(session[:original_user]).to eq instructor
           expect(session[:impersonate]).to be true
       end

Anonymizing Team Names

  1. Login as an instructor
  2. Switch to Anonymized view
  3. All team names should now be anonymized

Edge Cases :

  • In Anonymized view, clicking on Team details where team members are shown, team member names should be anonymized too.

 describe '#anonymized_view' do
   it 'returns anonymized name of team when anonymized view is set' do
     allow(User).to receive(:anonymized_view?).and_return(true)
     expect(team.name).to eq 'Anonymized_Team_' + team.id.to_s
     expect(team.name).not_to eq 'no team'
   end
   it 'returns real name of team when anonymized view is not set' do
     allow(User).to receive(:anonymized_view?).and_return(false)
     expect(team.name).not_to eq 'Team_' + team.id.to_s
     expect(team.name).to eq 'no team'
   end
 end 

Wrong Anonymized Names

All the bugs related to anonymized view are covered by code code changes made in the Pull Request and corresponding test cases are in place hence no need to write any test case for this section.

Team Information

  1. Shubham Dilip Pampattiwar (sdpampat)
  2. Pranav Maruti Gaikwad (pmgaikwa)
  3. Omkar Sunil Kulkarni (oskulkar)
  4. Deepayan Bardhan (dbardha)

Mentor: Sharique Khan (mkhan8)
Professor: Dr. Edward F. Gehringer (efg)

References

  1. Expertiza on GitHub