CSC/ECE 517 Fall 2023 - E2379. Reimplement authorization helper.rb: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 100: Line 100:
== New methods created==
== New methods created==
*jwt_verify_and_decode:
*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.
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.


Line 109: Line 110:
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 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.
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
*check_user_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 check_user_privileges(user_info, required_privilege)
      return false unless user_info.present? && user_info[: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
*given_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 given_user_can_submit?(token, user_id)
  given_user_can?(token, user_id, 'submit')
end
*given_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 given_user_can_review?(token, user_id)
  given_user_can?(token, user_id, 'review')
end
*given_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 given_user_can_take_quiz?(token, user_id)
  given_user_can?(token, user_id, 'take_quiz')
end
*given_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 given_user_can_read?(token, user_id)
  given_user_can_take_quiz?(token, user_id)
end





Revision as of 03:03, 4 December 2023

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

Introduction

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 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.

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

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.

Methods Details

  • Error Handling: The method is wrapped in a begin and rescue block to handle JWT::DecodeError. If an error occurs during the decoding process (e.g., an invalid signature or expired token), the method returns nil.

Error Handling

rescue JWT::DecodeError
  nil
end
  • check_user_privileges(user_info, required_privilege): The check_user_privileges method is responsible for determining whether a user, based on their role and claims stored in a JSON Web Token (JWT), possesses the required privilege within the context of the application.

Pseudo Code

function check_user_privileges(user_info, required_privilege):
   Check if User info is null
       return false
   switch statement:
           case 'Administrator':
           return user_info[:role] == 'Administrator' OR user_info[:role] == 'Super-Administrator'
           case 'Student':
           return user_info[:role] == 'Student' OR user_info[:role] == 'Teaching Assistant' OR user_info[:role] == 'Instructor' OR user_info[:role] == 'Administrator' OR user_info[:role] == 'Super-Administrator'     
           Other cases
           default:
            return false
  • Updating existing methods in the helper wherever session is being used.

Test Plan

  • JWT Authentication Integration

Objectives:

  • Verify JWT Token Processing:

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

  • User Privilege Verification:

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

  • User Identity Verification:

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

  • Existing Methods Update:

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

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.

Existing methods to be updated

  • current_user_has_super_admin_privileges
  • current_user_is_a?

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
  • check_user_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 check_user_privileges(user_info, required_privilege)
     return false unless user_info.present? && user_info[: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
  • given_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 given_user_can_submit?(token, user_id)
  given_user_can?(token, user_id, 'submit')
end
  • given_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 given_user_can_review?(token, user_id)
  given_user_can?(token, user_id, 'review')
end
  • given_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 given_user_can_take_quiz?(token, user_id)
  given_user_can?(token, user_id, 'take_quiz')
end
  • given_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 given_user_can_read?(token, user_id)
  given_user_can_take_quiz?(token, user_id)
end





Team

Mentor

  • Ameya Vaichalikar (agvaicha)

Members

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