CSC/ECE 517 Fall 2023 - E2379. Reimplement authorization helper.rb

From Expertiza_Wiki
Jump to navigation Jump to search

This wiki page is for the information regarding the changes made for the E2379 OSS assignment for Fall 2023, CSC/ECE 517.

Introduction

Expertiza is an open source project developed on Ruby on Rails. This web application is maintained by the student and faculty at NC State. This application gives complete control to the instructor to maintain the assignments in their class. With multiple functionalities such as adding topics, creating groups, and peer reviews, Expertiza is a well-developed application that can handle all types of assignments. To learn more about the full functionality Expertiza has to offer, visit the Expertiza wiki. The Expertiza currently uses session-based authentication in its AuthorizationHelper module. The reimplementation back end uses JSON Web Token (JWT) based authentication. This requires a redesign of the AuthorizationHelper module to accommodate JWT-based authentication.

About Authorization Helper

The AuthorizationHelper module provides methods to check a user's privileges and roles within the system. It allows you to determine if the current user has specific roles like Super-Admin, Admin, Instructor, TA, or Student. You can also check if the user is a participant in a particular assignment, instructs an assignment, or has TA mappings for an assignment. Additionally, it provides methods to identify if the current user can perform actions like submitting work, reviewing, or taking quizzes. These functions are essential for managing user permissions and access control in the application.

  • Role Verification:

The module allows you to determine if the current user possesses specific roles such as Super-Admin, Admin, Instructor, TA (Teaching Assistant), or Student. This is pivotal for identifying the user's overarching responsibilities and access levels within the system.

  • Assignment-related Checks:

You can use the module to check whether the user is a participant in a particular assignment, holds the role of an instructor for an assignment, or has TA mappings (Teaching Assistant assignments) associated with a specific task. These checks are instrumental in understanding the user's involvement and responsibilities in the context of assignments.

  • Action Permissions:

The AuthorizationHelper module provides methods to determine if the current user is allowed to perform specific actions. For instance, you can check if the user can submit work, conduct reviews, or take quizzes. This functionality ensures that user actions align with their assigned roles and responsibilities.

  • Access Control:

Through the aforementioned checks, the module facilitates robust access control by enabling you to tailor the user experience based on their roles and permissions. This is vital for maintaining system security and ensuring that users can only perform actions for which they are authorized.

Requirements

  • JWT Authentication Integration: Modify the AuthorizationHelper module to integrate JWT-based authentication, allowing users to authenticate and authorize requests using JWT tokens instead of sessions.
  • Token Verification: Implement methods to verify and decode JWT tokens to extract user information and permissions.
  • Privilege Verification: Update the existing methods (e.g., current_user_has_super_admin_privileges?, current_user_is_a?, etc) to use JWT claims to determine a user's privileges. Users will be granted access based on their role and claims within the JWT.
  • User Identity Verification: Implement a method to verify the identity of the current user based on the JWT token. Ensure that the user's role is validated correctly.
  • Methods should be updated to use the user's JWT claims.
  • Error Handling: Implement appropriate error handling to deal with JWT verification failures or unauthorized access attempts.


Methods to be implemented

  • jwt_verify_and_decode(token): This method will verify and decode a JWT token and return the user's information, including role and claims.
  • check_user_privileges(user_info, required_privilege): Given user information from the JWT and a required privilege, this method will determine if the user has the required privilege.
  • Update and adapt the existing methods to use JWT claims for authentication and authorization.

Deliverables

  • A modified and fully functional AuthorizationHelper module with JWT-based authentication.
  • Updated methods to ensure JWT claims are used for authentication and authorization.
  • Appropriate error handling to handle JWT-related issues.
  • Unit tests should cover different scenarios and edge cases to ensure that each function works as expected
  • Comments for every function.

JWT Token

JSON Web Tokens (JWT) are commonly used for implementing authorization in web applications. The AuthorizationHelper module can leverage JWT tokens to determine the privileges and roles of a user. Here's a general overview of how JWT tokens are typically used for authorization:

  • Token Generation:

When a user logs in or authenticates, a JWT token is generated on the server-side. This token typically includes information such as the user's ID, roles, and any other relevant claims.

  • Token Issuance:

The server issues the JWT token to the client after successful authentication. The client then stores this token, usually in a secure manner such as in an HTTP-only cookie or local storage.

  • Token Inclusion in Requests:

For subsequent requests to the server that require authorization, the client includes the JWT token in the request headers. This can be done using the "Authorization" header with a value like "Bearer <token>".

  • Token Verification on the Server:

The server-side AuthorizationHelper module receives the JWT token from the client in the request. It then verifies the token's integrity and authenticity using a secret key known only to the server.

  • Decoding the Token:

Once the token is verified, the server decodes its content. The decoded payload includes information about the user, such as their roles, which the AuthorizationHelper module can use for authorization checks.

  • Authorization Checks:

The AuthorizationHelper module performs checks based on the decoded information from the JWT token. For example, it may check if the user has the required roles or permissions to access certain resources or perform specific actions.

  • Access Granted or Denied:

Based on the results of the authorization checks, the server responds to the client's request by either allowing or denying access to the requested resource.

A JWT contains three parts:

  • Header: Consists of two parts: The signing algorithm that’s being used and the type of token.
  • Payload: The payload contains the claims or the JSON object.
  • Signature: A string that is generated via a cryptographic algorithm that can be used to verify the integrity of the JSON payload.

New methods created

  • jwt_verify_and_decode:

Description: This method is part of the JWT (JSON Web Token) Authentication Integration in the application. It serves the purpose of verifying and decoding a given JWT token using the JsonWebToken class. JWT tokens are commonly used for authentication and authorization purposes in web applications.

Parameters: token (String): The JWT token that needs to be verified and decoded.

Returns: If the provided JWT token is valid, the method decodes the token using the JsonWebToken.decode method. If decoding is successful, the decoded information is converted into a HashWithIndifferentAccess to provide easy access to the user information with indifferent access to symbol and string keys. If the token is invalid or decoding fails (raises JWT::DecodeError), the method returns nil.

Code

   def jwt_verify_and_decode(token)
     begin
       decoded_token = JsonWebToken.decode(token)
       return HashWithIndifferentAccess.new(decoded_token)
     rescue JWT::DecodeError
       return nil
     end
   end
  • user_has_needed_privileges?:

Description: This method is responsible for checking user privileges based on the claims provided in a JWT (JSON Web Token). Given the user information extracted from the JWT and a required privilege, the method determines if the user possesses the specified privilege. The method primarily validates the user's role against the required privilege.

Parameters: user_info (HashWithIndifferentAccess): User information extracted from the JWT, typically containing details like the user's role.

required_privilege (String): The required privilege that the method checks against the user's role. Returns: true if the user possesses the required privilege. false otherwise, including cases where the user information is not present (nil) or lacks a role.

Code:

   def user_has_needed_privileges?(user_rights, required_privilege)
       return false unless user_rights.present? && user_rights['role'].present?
   
       case required_privilege
       when 'Super-Administrator'
       return user_info['role'] == 'Super-Administrator'
       when 'Administrator'
       return user_info['role'] == 'Administrator'
       when 'Instructor'
       return user_info['role'] == 'Instructor'
       when 'Teaching Assistant'
       return user_info['role'] == 'Teaching Assistant'
       when 'Student'
       return user_info['role'] == 'Student'
       else
       return false
       end
   end

Existing methods updated

  • current_user_has_super_admin_privileges:

Determine if the currently logged-in user has the privileges of a Super-Admin. It checks if the user has Super-Administrator privileges based on the JWT token. The method calls check_user_privileges with the appropriate privilege and returns the result.

Old Code:

def current_user_has_super_admin_privileges?
  current_user_has_privileges_of?('Super-Administrator')
end

Updated Code:

def current_user_has_super_admin_privileges?(token)
  user_info = jwt_verify_and_decode(token)
  return check_user_privileges(user_info, 'Super-Administrator') if user_info.present?
  false
end


  • current_user_has_admin_privileges:

Determine if the currently logged-in user has the privileges of an Admin (or higher). It checks if the user has Administrator privileges based on the JWT token. The method calls check_user_privileges with the appropriate privilege and returns the result.

Old Code:

def current_user_has_admin_privileges?
  current_user_has_privileges_of?('Administrator')
end


Updated Code:

def current_user_has_admin_privileges?(token)
  user_info = jwt_verify_and_decode(token)
  return check_user_privileges(user_info, 'Administrator') if user_info.present?
  false
end
  • current_user_has_instructor_privileges:

Determine if the currently logged-in user has the privileges of an Instructor (or higher). It checks if the user has Instructor privileges based on the JWT token. The method calls check_user_privileges with the appropriate privilege and returns the result.

Old Code:

def current_user_has_instructor_privileges?
  current_user_has_privileges_of?('Instructor')
end


Updated Code:

def current_user_has_instructor_privileges?(token)
  user_info = jwt_verify_and_decode(token)
  return check_user_privileges(user_info, 'Instructor') if user_info.present?
  false
end
  • current_user_has_ta_privileges:

Determine if the currently logged-in user has the privileges of an Instructor (or higher). It checks if the user has Instructor privileges based on the JWT token. The method calls check_user_privileges with the appropriate privilege and returns the result.

Old Code:

def current_user_has_ta_privileges?
  current_user_has_privileges_of?('Teaching Assistant')
end


Updated Code:

def current_user_has_ta_privileges?(token)
  user_info = jwt_verify_and_decode(token)
  return check_user_privileges(user_info, 'Teaching Assistant') if user_info.present?
  false
end


  • current_user_has_student_privileges:

Determine if the currently logged-in user has the privileges of a Student (or higher). It checks if the user has Student privileges based on the JWT token. The method calls check_user_privileges with the appropriate privilege and returns the result.

Old Code:

def current_user_has_student_privileges?
  current_user_has_privileges_of?('Student')
end

Updated Code:

def current_user_has_student_privileges?(token)
  user_info = jwt_verify_and_decode(token)
  return check_user_privileges(user_info, 'Student') if user_info.present?
  false
end


  • current_user_is_assignment_participant:

Determine if the currently logged-in user is participating in an Assignment based on the assignment_id argument. It checks if the user is a participant in a specific assignment based on the JWT token.

Old Code:

def current_user_is_assignment_participant?(assignment_id)
  if user_logged_in?
    return AssignmentParticipant.exists?(parent_id: assignment_id, user_id: session[:user].id)
  end
  false
end

Updated Code:

def current_user_is_assignment_participant?(token, assignment_id)
  user_info = jwt_verify_and_decode(token)
  return AssignmentParticipant.exists?(parent_id: assignment_id, user_id: user_info[:id]) if user_info.present?
  false
end


  • current_user_teaching_staff_of_assignment:

Determine if the currently logged-in user is teaching staff of an Assignment based on the assignment_id argument. It checks if the user is an instructor or has TA mapping for a specific assignment based on the JWT token.


Old Code:

def current_user_teaching_staff_of_assignment?(assignment_id)
  assignment = Assignment.find(assignment_id)
  user_logged_in? &&
    (
        current_user_instructs_assignment?(assignment) ||
        current_user_has_ta_mapping_for_assignment?(assignment)
      )
end

Updated Code:

def current_user_teaching_staff_of_assignment?(token, assignment_id)
  assignment = Assignment.find(assignment_id)
  user_info = jwt_verify_and_decode(token)
  user_info.present? &&
    (
      current_user_instructs_assignment?(assignment, user_info) ||
      current_user_has_ta_mapping_for_assignment?(assignment, user_info)
    )
end
  • current_user_is_a:

Determine if the currently logged-in user IS of the given role name. It checks if the user's role in the JWT token matches the provided role name.

Old Code:

def current_user_is_a?(role_name)
  current_user_and_role_exist? && session[:user].role.name == role_name
end

Updated Code:

def current_user_is_a?(token, role_name)
  user_info = jwt_verify_and_decode(token)
  return user_info.present? && user_info[:role] == role_name
end
  • current_user_has_id:

Determine if the current user has the passed-in id value. It checks if the user's ID in the JWT token matches the provided ID.

Old Code:

def current_user_has_id?(id)
  user_logged_in? && session[:user].id.eql?(id.to_i)
end

Updated Code:

def current_user_has_id?(token, id)
  user_info = jwt_verify_and_decode(token)
  return user_info.present? && user_info[:id].to_i == id.to_i
end
  • current_user_created_bookmark_id:

Determine if the currently logged-in user created the bookmark with the given ID. It checks if the user in the JWT token created the bookmark with the specified ID.

Old Code:

def current_user_created_bookmark_id?(bookmark_id)
  user_logged_in? && !bookmark_id.nil? && Bookmark.find(bookmark_id.to_i).user_id == session[:user].id
rescue ActiveRecord::RecordNotFound
  false
end

Updated Code:

def current_user_created_bookmark_id?(token, bookmark_id)
  user_info = jwt_verify_and_decode(token)
  return user_info.present? &&
         !bookmark_id.nil? &&
         Bookmark.find(bookmark_id.to_i).user_id == user_info[:id]
rescue ActiveRecord::RecordNotFound
  false
end
  • current_user_can_submit:

Determine if the given user can submit work. It checks if the user in the JWT token can submit work.

Old Code:

def given_user_can_submit?(user_id)
  given_user_can?(user_id, 'submit')
end

Updated Code:

def current_user_can_submit?(token, user_id)
  current_user_can?(token, user_id, 'submit')
end
  • current_user_can_review:

Determine if the given user can review work. It checks if the user in the JWT token can review work.

Old Code:

def given_user_can_review?(user_id)
  given_user_can?(user_id, 'review')
end

Updated Code :

def current_user_can_review?(token, user_id)
  current_user_can?(token, user_id, 'review')
end
  • current_user_can_take_quiz:

Determine if the given user can take quizzes. It checks if the user in the JWT token can take quizzes.

Old Code:

def given_user_can_take_quiz?(user_id)
  given_user_can?(user_id, 'take_quiz')
end

Updated Code:

def current_user_can_take_quiz?(token, user_id)
  current_user_can?(token, user_id, 'take_quiz')
end
  • current_user_can_read:

Determine if the given user can read work. It checks if the user in the JWT token can read work.

Old Code:

def given_user_can_read?(user_id)
   given_user_can_take_quiz?(user_id)
 end

Updated Code :

def current_user_can_read?(token, user_id)
  current_user_can_take_quiz?(token, user_id)
end

Test Plan

  • JWT Authentication Integration

Objectives:

  • Verify JWT Token Processing:

Confirm that JWT tokens are correctly processed, verified, and decoded.

Token Encoding: Ensure that a user's information (e.g., user_id, role) is properly encoded into a JWT token during the authentication process.

Token Decoding: Implement a method to decode and verify JWT tokens. Confirm that the decoding process is successful and returns the expected user information.

Token Expiry Handling: Check that the system handles token expiry properly and denies access or requests re-authentication when a token is expired.

Token Signature Verification: Validate that the system verifies the signature of the JWT token to ensure its authenticity.

  • User Privilege Verification:

Ensure that user privileges are correctly verified based on JWT claims.

Role-Based Access Control (RBAC): Integrate RBAC using JWT claims to determine user roles. Update methods that check for specific privileges to rely on JWT claims instead of other mechanisms.

Privilege Validation Methods: Update existing methods (e.g., current_user_has_admin_privileges?) to utilize information from JWT claims. Ensure that the system grants or denies access based on the user's role and privileges stored in the JWT token.

  • User Identity Verification:

Confirm that the user's identity is accurately verified based on the JWT token.

User ID Verification: Update methods that validate the user's identity (current_user_has_id?) to use the user_id claim from the JWT token. Ensure that the system checks the user's ID against the one provided in the token.

User Role Verification: Implement methods to check if the user has a specific role (current_user_is_a?) based on JWT claims. Verify that the system accurately identifies the user's role using the JWT token.

  • Existing Methods Update:

Validate that existing methods are successfully updated to use JWT claims.

Method Refactoring: Refactor existing authentication and authorization methods to utilize JWT claims for user information. Ensure that methods no longer rely on separate data stores for user roles or privileges.

Unit Testing: Create unit tests for each updated method to ensure that they work correctly with JWT claims. Verify that the methods return expected results for various scenarios, such as different roles and expired tokens.

Test Cases

  • JWT Token Processing:

Verify that a valid JWT token is successfully processed and decoded. Test with an invalid token to ensure proper error handling.

  • User Privilege Verification:

Check user privileges for various roles (super admin, admin, instructor, TA, student). Verify user privileges for specific actions (submitting work, reviewing, etc.).

  • Error Handling:

Test error handling for JWT verification failures. Attempt unauthorized access and validate appropriate error handling.

  • User Identity Verification:

Verify the correct identification of the current user based on the JWT token. Test with various roles to ensure accurate identification.

  • Existing Methods Update:

Confirm that existing methods are updated to use JWT claims for authentication and authorization. Validate the behavior of updated methods with various scenarios.

  • Generate a JWT token using Postman:

1.Open Postman and create a new request or collection.

2.In the request, go to the "Authorization" tab and choose "Bearer Token" as the type.

3.Click "Get New Access Token" to open the "Token" modal.

4.Configure the token with a name, grant type, token endpoint URL, client ID, secret, scope, etc.

5.Click "Request Token" to obtain the token, and enter additional credentials if prompted.

6.The generated token is displayed in the "Token" modal and is automatically added to the request's "Authorization" header.

7.Use the token to authenticate subsequent requests.

8.Configure Postman to handle token expiry and automatic token refresh if needed.

9.Save the token configuration for reuse.

  • Decode JWT Token:

The JWT token can be decoded using jwt.io.

  • Run the rails server and confirm its successful execution:
  • Rspec Test cases file:

Rspec test cases file is run, and all the tests have passed.

For more detailed explanation of the project, here is the Demo Link.

Team

Mentor

  • Ameya Vaichalikar (agvaicha)

Members

  • Sravya Karanam (skarana)
  • Sucharitha Nadendla (snadend3)
  • Abhishek Desai (adesai7)

Pull Request

Pull Request