CSC/ECE 517 Spring 2024 - E2421. Reimplement impersonating users (within impersonate controller.rb): Difference between revisions
No edit summary |
No edit summary |
||
(5 intermediate revisions by the same user not shown) | |||
Line 6: | Line 6: | ||
The objective is to reimplement the backend code for the impersonation feature in the new implementation of Expertiza. The current implementation relies on sessions, which won't be compatible with the new implementation using JWT tokens for authentication and returning JSON responses. The challenge lies in transitioning the logic from session-based management to JWT-based authentication while maintaining the functionality of impersonating users. The reimplementation involves planning how the backend will communicate with the frontend, potentially requiring changes in existing or new files beyond the impersonate_controller.rb. | The objective is to reimplement the backend code for the impersonation feature in the new implementation of Expertiza. The current implementation relies on sessions, which won't be compatible with the new implementation using JWT tokens for authentication and returning JSON responses. The challenge lies in transitioning the logic from session-based management to JWT-based authentication while maintaining the functionality of impersonating users. The reimplementation involves planning how the backend will communicate with the frontend, potentially requiring changes in existing or new files beyond the impersonate_controller.rb. | ||
==Implementation== | |||
===UML Diagram=== | |||
The following UML diagram shows the association between models we will be working on | |||
[[File:Uml reimplement impersonate.png]] | |||
===Hierarchy Diagram=== | |||
Visualization of the hierarchical structure | |||
[[File:User-hierarchy.jpg]] | |||
===Request=== | |||
*GET: {BASE_URL}/api/v1/impersonate/:username | |||
- Response: | |||
"message": "Successfully Fetched User List!", | |||
"userList": [ | |||
{ | |||
"id": 4, | |||
"name": "Mihir", | |||
"full_name": "Mihir BHanderi", | |||
"email": "mbhande@example.com", | |||
"email_on_review": false, | |||
"email_on_submission": false, | |||
"email_on_review_of_review": false, | |||
"role": { | |||
"id": 4, | |||
"name": "Teaching Assistant" | |||
}, | |||
"institution": { | |||
"id": 1, | |||
"name": "North Carolina State University" | |||
}, | |||
"parent": { | |||
"id": null, | |||
"name": null | |||
} | |||
} | |||
], | |||
"success": true | |||
*POST: {BASE_URL}/api/v1/impersonate | |||
- Payload: impersonate_id | |||
- Response: | |||
"message": "Successfully Impersonated Ketul!", | |||
"token": "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MywibmFtZSI6IktldHVsIiwiZnVsbF9uYW1lIjoiS2V0dWwgQ2hheXlhIiwicm9sZSI6Ikluc3RydWN0b3IiLCJpbnN0aXR1dGlvbl9pZCI6MSwiaW1wZXJzb25hdGVkIjp0cnVlLCJvcmlnaW5hbF91c2VyIjoiIzxVc2VyOjB4MDAwMDdmMzVmYTc5YjVmOD4iLCJleHAiOjE3MTEzMjc0MTV9.U9wDOT618UCkf25MnCiK8W3ybeZv5BSQDNTEPOMUDABAvDd0HWSj3kIGccITHaoVsIykZFsyUDY3rL_M32zmfEXvxZuEleWSqUZGxbjQRIFP1bR_Q5sPESoBdlxVJ4QG8sUGQtuhOzMyH3z4R4ruhz1JpsQlalVZQHCbdtJOI9B4WKhNJ98Dls1fefnzYwLMnTr6e3ttbGGGK5Bm8zSpPvIWmCVNoueKHNptFcNejbU4Mt9RHWHLsTwdAtuywNLCu7li7RRNXo00D5JOUMxL7eB5AiQRpxah8BF7b0lM_Xh7bB56WvD5JTjoNZu3c3AK_EJksGXiFxwlPzNRc8Q", | |||
"success": true | |||
===Impersonate Controller=== | |||
The ImpersonateController facilitates user impersonation functionality. It includes methods to fetch a list of users available for impersonation based on a provided username parameter and to impersonate a selected user by generating a new JWT token with the necessary user information. The controller ensures that impersonation requests are handled securely, validating permissions before allowing impersonation to occur. | |||
*impersonate_controller.rb file | |||
<pre> | |||
class Api::V1::ImpersonateController < ApplicationController | |||
# Fetches users to impersonate whose name match the passed parameter | |||
def get_users_list | |||
users = current_user.get_available_users(params[:user_name]) | |||
render json: { message: "Successfully Fetched User List!", userList:users, success:true }, status: :ok | |||
end | |||
def check_if_user_impersonateable? | |||
impersonate_user = User.find_by(id: params[:impersonate_id]) | |||
if impersonate_user | |||
return current_user.can_impersonate? impersonate_user | |||
end | |||
false | |||
end | |||
# Impersonates a new user and returns new jwt token | |||
def impersonate | |||
unless params[:impersonate_id].present? | |||
render json: { error: "impersonate_id is required", success:false }, status: :unprocessable_entity | |||
return | |||
end | |||
if check_if_user_impersonateable? | |||
impersonate_user = User.find_by(id: params[:impersonate_id]) | |||
payload = { id: impersonate_user.id, name: impersonate_user.name, full_name: impersonate_user.full_name, role: impersonate_user.role.name, | |||
institution_id: impersonate_user.institution.id, impersonated:true, original_user: current_user } | |||
impersonate_user_token = JsonWebToken.encode(payload, 24.hours.from_now) | |||
render json: { message: "Successfully Impersonated #{impersonate_user.name}!", token:impersonate_user_token, success:true }, status: :ok | |||
else | |||
render json: { error: "You do not have permission to impersonate this user", success:false }, status: :forbidden | |||
end | |||
end | |||
end | |||
</pre> | |||
===User=== | |||
These methods extend the functionality of the User model. The get_available_users method retrieves users whose full names match a provided parameter. can_impersonate? determines whether the user has the authority to impersonate another user based on their role hierarchy. teaching_assistant_for? checks if the user is a teaching assistant for a given student, and teaching_assistant? determines if the user is a teaching assistant based on their role. Lastly, recursively_parent_of recursively checks for parent-child relationships between user roles. | |||
*user.rb file | |||
<pre> | |||
# Check if the user can impersonate another user | |||
def can_impersonate?(user) | |||
return true if role.super_administrator? | |||
return true if recursively_parent_of(user.role) | |||
false | |||
end | |||
# Check if the user is a teaching assistant for the student's course | |||
def teaching_assistant_for?(student) | |||
return false unless teaching_assistant? | |||
return false unless student.role.name == 'Student' | |||
# We have to use the Ta object instead of User object | |||
# because single table inheritance is not currently functioning | |||
ta = Ta.find(id) | |||
ta.managed_users.each do |user| | |||
return true if user.id == student.id | |||
end | |||
false | |||
end | |||
# Check if the user is a teaching assistant | |||
def teaching_assistant? | |||
true if role.ta? | |||
end | |||
# Recursively check if parent child relationship exists | |||
def recursively_parent_of(user_role) | |||
p = user_role.parent | |||
return false if p.nil? | |||
return true if p == self.role | |||
return false if p.super_administrator? | |||
recursively_parent_of(p) | |||
end | |||
</pre> | |||
== Files added / modified == | |||
*reimplementation-back-end/app/models/user.rb | |||
*reimplementation-back-end/app/controllers/api/v1/impersonate_controller.rb | |||
*reimplementation-back-end/config/routes.rb | |||
==List of Users in Database== | |||
{| class="wikitable" | |||
|- | |||
! ID !! Email !! Password !! Role | |||
|- | |||
| 1 || admin2@example.com || password123 || Super-Administrator | |||
|- | |||
| 2 || jay@example.com || password123 || Administration | |||
|- | |||
| 3 || k2l@example.com || password123 || Instructor | |||
|- | |||
| 4 || mbhande@example.com || password123 || TA | |||
|- | |||
| 7 || dpatesl@example.com || password123 || Student | |||
|} | |||
==Testing on Postman== | ==Testing on Postman== | ||
Postman was used to manually test the additional method in impersonate_controller.rb, as well as the actions and routes of the corresponding controllers. Before testing any of these methods with Postman, submit a request to /login using the user_name and password fields, which will send an authentication token. This token must be added to Postman's 'Authorization' tab as a 'Bearer token' before any further requests can be made. | |||
Postman was used to manually test the additional method in impersonate_controller.rb, as well as the actions and routes of the corresponding controllers. Before testing any of these methods with Postman, submit a request to /login using the user_name and password fields, which will send an authentication token. This token must be added to Postman's 'Authorization' tab as a 'Bearer token' before any further requests can be made. Postman collection [https://www.postman.com/blue-water-189861/workspace/oodd-project-3/collection/13428669-4d663583-0b90-4316-bd6c-9441ea576085?action=share&creator=13428669 link] | |||
*First of all, fork the expertiza workspace in order to work upon it | |||
[[File:Fork-expertiza-postman.png | 500px]] | |||
*Fetch User List which can be impersonated (For eg: If Instructor fetches list he can see matched TAs and Students only) | |||
[[File:Userlist.jpg | 500px]] | |||
* Login with provided credentials and copy the token | * Login with provided credentials and copy the token | ||
Line 24: | Line 194: | ||
*Failure message if the user is not impersonable (Here instructor is trying to impersonate admin) | *Failure message if the user is not impersonable (Here instructor is trying to impersonate admin) | ||
[[File:Inst-admin.jpg | 500px]] | [[File:Inst-admin.jpg | 500px]] | ||
Here is the video of successfully running the tests [https://www.loom.com/share/b1b6b7e52a884a2f8e097e25a53e77f3?sid=e80f4d44-4667-4874-8ef8-de10f8ac1bb2] | |||
==Team== | ==Team== | ||
Line 33: | Line 205: | ||
*Jay Patel <jhpatel9@ncsu.edu><br> | *Jay Patel <jhpatel9@ncsu.edu><br> | ||
*Mihir Bhanderi <mbhande2@ncsu.edu><br> | *Mihir Bhanderi <mbhande2@ncsu.edu><br> | ||
==Pull Request== | |||
*[https://github.com/expertiza/reimplementation-back-end/pull/88] | |||
==References== | |||
*[https://docs.google.com/document/d/1MzdTjsULAMI1ZJRbf-xvThkSxxUXjk-hNMQ9DPdhmrI/edit#heading=h.vh7jk2b6nlb2 Project Instructions] |
Latest revision as of 23:42, 24 March 2024
Expertiza
Expertiza is a Ruby on Rails based open source project. Instructors have the ability to add new projects, assignments, etc., as well as edit existing ones. Later on, they can view student submissions and grade them. Students can also use Expertiza to organize into teams to work on different projects and assignments and submit their work. They can also review other students' submissions.
Project Overview
The objective is to reimplement the backend code for the impersonation feature in the new implementation of Expertiza. The current implementation relies on sessions, which won't be compatible with the new implementation using JWT tokens for authentication and returning JSON responses. The challenge lies in transitioning the logic from session-based management to JWT-based authentication while maintaining the functionality of impersonating users. The reimplementation involves planning how the backend will communicate with the frontend, potentially requiring changes in existing or new files beyond the impersonate_controller.rb.
Implementation
UML Diagram
The following UML diagram shows the association between models we will be working on
Hierarchy Diagram
Visualization of the hierarchical structure
Request
- GET: {BASE_URL}/api/v1/impersonate/:username
- Response:
"message": "Successfully Fetched User List!", "userList": [ { "id": 4, "name": "Mihir", "full_name": "Mihir BHanderi", "email": "mbhande@example.com", "email_on_review": false, "email_on_submission": false, "email_on_review_of_review": false, "role": { "id": 4, "name": "Teaching Assistant" }, "institution": { "id": 1, "name": "North Carolina State University" }, "parent": { "id": null, "name": null } } ], "success": true
- POST: {BASE_URL}/api/v1/impersonate
- Payload: impersonate_id - Response:
"message": "Successfully Impersonated Ketul!", "token": "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MywibmFtZSI6IktldHVsIiwiZnVsbF9uYW1lIjoiS2V0dWwgQ2hheXlhIiwicm9sZSI6Ikluc3RydWN0b3IiLCJpbnN0aXR1dGlvbl9pZCI6MSwiaW1wZXJzb25hdGVkIjp0cnVlLCJvcmlnaW5hbF91c2VyIjoiIzxVc2VyOjB4MDAwMDdmMzVmYTc5YjVmOD4iLCJleHAiOjE3MTEzMjc0MTV9.U9wDOT618UCkf25MnCiK8W3ybeZv5BSQDNTEPOMUDABAvDd0HWSj3kIGccITHaoVsIykZFsyUDY3rL_M32zmfEXvxZuEleWSqUZGxbjQRIFP1bR_Q5sPESoBdlxVJ4QG8sUGQtuhOzMyH3z4R4ruhz1JpsQlalVZQHCbdtJOI9B4WKhNJ98Dls1fefnzYwLMnTr6e3ttbGGGK5Bm8zSpPvIWmCVNoueKHNptFcNejbU4Mt9RHWHLsTwdAtuywNLCu7li7RRNXo00D5JOUMxL7eB5AiQRpxah8BF7b0lM_Xh7bB56WvD5JTjoNZu3c3AK_EJksGXiFxwlPzNRc8Q", "success": true
Impersonate Controller
The ImpersonateController facilitates user impersonation functionality. It includes methods to fetch a list of users available for impersonation based on a provided username parameter and to impersonate a selected user by generating a new JWT token with the necessary user information. The controller ensures that impersonation requests are handled securely, validating permissions before allowing impersonation to occur.
- impersonate_controller.rb file
class Api::V1::ImpersonateController < ApplicationController # Fetches users to impersonate whose name match the passed parameter def get_users_list users = current_user.get_available_users(params[:user_name]) render json: { message: "Successfully Fetched User List!", userList:users, success:true }, status: :ok end def check_if_user_impersonateable? impersonate_user = User.find_by(id: params[:impersonate_id]) if impersonate_user return current_user.can_impersonate? impersonate_user end false end # Impersonates a new user and returns new jwt token def impersonate unless params[:impersonate_id].present? render json: { error: "impersonate_id is required", success:false }, status: :unprocessable_entity return end if check_if_user_impersonateable? impersonate_user = User.find_by(id: params[:impersonate_id]) payload = { id: impersonate_user.id, name: impersonate_user.name, full_name: impersonate_user.full_name, role: impersonate_user.role.name, institution_id: impersonate_user.institution.id, impersonated:true, original_user: current_user } impersonate_user_token = JsonWebToken.encode(payload, 24.hours.from_now) render json: { message: "Successfully Impersonated #{impersonate_user.name}!", token:impersonate_user_token, success:true }, status: :ok else render json: { error: "You do not have permission to impersonate this user", success:false }, status: :forbidden end end end
User
These methods extend the functionality of the User model. The get_available_users method retrieves users whose full names match a provided parameter. can_impersonate? determines whether the user has the authority to impersonate another user based on their role hierarchy. teaching_assistant_for? checks if the user is a teaching assistant for a given student, and teaching_assistant? determines if the user is a teaching assistant based on their role. Lastly, recursively_parent_of recursively checks for parent-child relationships between user roles.
- user.rb file
# Check if the user can impersonate another user def can_impersonate?(user) return true if role.super_administrator? return true if recursively_parent_of(user.role) false end # Check if the user is a teaching assistant for the student's course def teaching_assistant_for?(student) return false unless teaching_assistant? return false unless student.role.name == 'Student' # We have to use the Ta object instead of User object # because single table inheritance is not currently functioning ta = Ta.find(id) ta.managed_users.each do |user| return true if user.id == student.id end false end # Check if the user is a teaching assistant def teaching_assistant? true if role.ta? end # Recursively check if parent child relationship exists def recursively_parent_of(user_role) p = user_role.parent return false if p.nil? return true if p == self.role return false if p.super_administrator? recursively_parent_of(p) end
Files added / modified
- reimplementation-back-end/app/models/user.rb
- reimplementation-back-end/app/controllers/api/v1/impersonate_controller.rb
- reimplementation-back-end/config/routes.rb
List of Users in Database
ID | Password | Role | |
---|---|---|---|
1 | admin2@example.com | password123 | Super-Administrator |
2 | jay@example.com | password123 | Administration |
3 | k2l@example.com | password123 | Instructor |
4 | mbhande@example.com | password123 | TA |
7 | dpatesl@example.com | password123 | Student |
Testing on Postman
Postman was used to manually test the additional method in impersonate_controller.rb, as well as the actions and routes of the corresponding controllers. Before testing any of these methods with Postman, submit a request to /login using the user_name and password fields, which will send an authentication token. This token must be added to Postman's 'Authorization' tab as a 'Bearer token' before any further requests can be made. Postman collection link
- First of all, fork the expertiza workspace in order to work upon it
- Fetch User List which can be impersonated (For eg: If Instructor fetches list he can see matched TAs and Students only)
- Login with provided credentials and copy the token
- In the Expertiza/Authorization paste the token and save
- Put the id for the user to be impersonated
- Success message if the user is impersonable
- Failure message if the user is not impersonable (Here instructor is trying to impersonate admin)
Here is the video of successfully running the tests [1]
Team
Mentor
- Chetana Chunduru <cchetan2@ncsu.edu>
Students
- Devansh Shah <dshah8@ncsu.edu>
- Jay Patel <jhpatel9@ncsu.edu>
- Mihir Bhanderi <mbhande2@ncsu.edu>