CSC/ECE 517 Fall 2024 - E2487. Reimplement authorization helper.rb

From Expertiza_Wiki
Jump to navigation Jump to search

Introduction

Expertiza currently uses session-based authentication in its AuthorizationHelper module. However, the new back-end reimplementation adopts JSON Web Token (JWT) for authentication. This transition is necessary to make the system more scalable, stateless, and secure. JWT-based authentication will replace session-based methods, requiring updates to the AuthorizationHelper module and associated methods.

Background

The AuthorizationHelper module in Expertiza plays a critical role in managing user permissions and access control within the system. It provides methods to verify a user’s privileges based on their assigned roles, such as Super-Admin, Admin, Instructor, TA, or Student. These methods check if the user is authorized to perform actions like submitting assignments, reviewing work, or participating in quizzes. Additionally, it determines whether a user is involved in specific assignments, either as a participant, instructor, or through TA mappings.

Problem Statement

Expertiza currently uses session-based authentication in its [AuthorizationHelper] module. The [reimplementation-back-end] however, uses JWT (JSON Web Token) based authentication. This requires a reimplementation of an authorization concern module to accommodate JWT-based authentication

Concerns

A concern is a module that you extract to split the implementation of a class or module in coherent chunks, instead of having one big class body. Our new authorization module will be implemented as a concern which will work in tandem with the already-implemented [JWT Concern]

The following points need to be taken care of as per the requirement

  • Role Management :
    • Enable the system to process, verify, and decode roles from JWT Tokens.
    • Use token role data to authenticate and authorize user actions.
  • Privilege Verification :
    • Update methods to validate user privileges using JWT claims.
  • Testing and Documentation :
    • Write comprehensive unit tests for all JWT-related methods.
    • Include clear documentation and comments to aid future developers.

Implemented Changes

JWT Token Overview

A JSON Web Token (JWT) is a compact, URL-safe token format commonly used for securely transmitting information between parties. It is widely used in web applications for authentication and authorization, as it allows information to be verified and trusted. In the context of our reimplemented AuthorizationHelper module, JWT tokens will replace session-based authentication, enabling stateless and more secure access control.

A JWT Token consists of three main parts:

  • Header: Contains metadata about the token, such as the type (JWT) and the hashing algorithm used (e.g., HS256 or RS256).
  • Payload: Includes claims, which are statements about an entity (typically, the user) and additional data like the user’s role and permissions.
  • Signature: A cryptographic signature generated by encoding the header and payload with a secret key or private key, ensuring data integrity.

These three sections are separated by dots (.), forming a token that looks like this: <Header>.<Payload>.<Signature>

Overall Flow

Methods Implemented

  • has_required_role?(required_role):
 def has_required_role?(required_role)
    required_role = Role.find_by_name(required_role) if required_role.is_a?(String)
    current_user&.role&.all_privileges_of?(required_role) || false
 end

This function checks if the current user has the required role or higher privileges. The role name is passed down as a parameter in required_role which is the minimum required role level for an action

For example:

has_required_role?('Administrator') # Checks if user is an admin or higher
has_required_role?(Role::INSTRUCTOR) # Checks if user is an instructor or higher


For exact role requirements, we also have:

  • is_role?(required_role):
 def is_role?(required_role)
    required_role = required_role.name if required_role.is_a?(Role)
    current_user&.role&.name == required_role
 end

which checks if the user has exactly the specified role. For example:

is_role?('Student') # True only if user is exactly a student 



Finally, we have authorization methods which work with the ApplicationController as well as individual resource controllers:


  # Authorize all actions
  def authorize
    unless all_actions_allowed?
      render json: { 
        error: "You are not authorized to #{params[:action]} this #{params[:controller]}"
      }, status: :forbidden
    end
  end

  # Check if all actions are allowed
  def all_actions_allowed?
    return true if has_required_role?('Administrator')
    action_allowed?
  end

  # Base action_allowed? - allows everything by default
  # Controllers should override this method to implement their authorization logic
  def action_allowed?
    true
  end


The individual resource controllers can then override the method 'action_allowed?' to manage access. For example:

  def action_allowed?
    has_required_role?('Instructor')
  end

Existing File Updates

This file was updated to include the new authorization module

 class ApplicationController < ActionController::API
   include Authorization
   include JwtToken
  
   before_action :authorize
 end


This ensures that the ['has_required_role?'] method is available globally along with ['is_role?']. These methods can then be used to override 'action_allowed?' to override the base implementation and provide more granular control



To test the functionality, based on the old Expertiza, the method was also overridden in the courses_controller.rb file:

 class Api::V1::CoursesController < ApplicationController
   before_action :set_course, only: %i[ show update destroy add_ta view_tas remove_ta copy ]
   rescue_from ActiveRecord::RecordNotFound, with: :course_not_found
   rescue_from ActionController::ParameterMissing, with: :parameter_missing

   def action_allowed?
     has_required_role?('Instructor')
   end

   ...

Anticipated Outcomes

1. Enhanced AuthorizationHelper Module with JWT Integration:
Updated and operational AuthorizationHelper module that would use JWT-based authentication. The module would be restructured to incorporate token-based authentication checks, replacing prior session-based methods.

2. Updated Associated Methods:
Methods within the module will have to be updated to leverage JWT claims, ensuring authentication and authorization are handled based on token data. This will allow the module to verify user roles and permissions effectively through JWT claims.

3. Comprehensive JWT Error Handling:
Built-in error handling to manage potential JWT-related issues, such as token expiration, tampering, or invalid claims. This ensures that unauthorized access is blocked, and clear error messages are provided when issues arise.

4. Robust Unit Tests for Validation:
A full suite of unit tests covering various scenarios, including normal, boundary, and edge cases, to validate each method’s functionality and confirm that all JWT-based authentication flows work reliably.

5. Detailed Documentation and Code Comments:
In-depth documentation and comments would be added to each function, explaining purpose, usage, and JWT-related adjustments, aiding in code readability and future maintenance.

Design Principles

The redesign of the `AuthorizationHelper` module for JWT-based authentication will follow well-established design principles to ensure the solution is secure, maintainable, and scalable. Below are the key principles to be applied and how they will shape this redesign:

1. Single Responsibility Principle (SRP)

  • Definition: Each module or class should have one clear responsibility.
  • How It’s Applied: The `AuthorizationHelper` module focuses only on managing authentication and authorization, separating these concerns into distinct methods. For instance, `jwt_verify_and_decode` handles token verification, while `check_user_privileges` focuses on role-based checks. This keeps the code clean and easier to maintain.

2. Separation of Concerns

  • Definition: Different aspects of the system should be handled independently.
  • How It’s Applied: JWT-related tasks like token creation and decoding are delegated to the `JsonWebToken` class, while privilege checking and user role management remain within `AuthorizationHelper`. This modular approach ensures changes in one area don’t unnecessarily affect the other.

3. Security by Design

  • Definition: Security should be built into the system from the start, not as an afterthought.
  • How It’s Applied: JWT tokens are validated for expiry and tampering before any actions are authorized. Sensitive data like secret keys is securely stored in environment variables, and communication involving tokens is secured using HTTPS. The system also uses role-based access control (RBAC) to enforce fine-grained permissions.

4. Statelessness

  • Definition: The system should not depend on maintaining user-specific data on the server.
  • How It’s Applied: By using JWT, all necessary information about the user is encoded in the token itself. This removes the dependency on server-side session storage, making the system scalable and suitable for distributed architectures.

5. Testability

  • Definition: Code should be designed to make testing straightforward and effective.
  • How It’s Applied: Each method has a single responsibility, making it easier to write focused tests. The modular design allows us to test components like token verification and privilege checking independently, while mock tokens can simulate various scenarios (valid, expired, tampered).

---

Test Plan

Unit Tests

Definition: Unit tests verify that individual methods work correctly in isolation under different scenarios.

Tests for jwt_verify_and_decode

Valid Token Test:

  • Input: A valid JWT token with claims like `id`, `role`, and `exp`.
  • Expected Output: Decoded payload as a `HashWithIndifferentAccess`.
  • Validation: Ensure the returned payload matches the expected data.

Expired Token Test:

  • Input: A token with an `exp` claim that has already passed.
  • Expected Output: `nil` with an appropriate error logged.
  • Validation: Confirm that the method gracefully handles expired tokens.

Invalid Token Test:

  • Input: A tampered JWT token with an invalid signature.
  • Expected Output: `nil`.
  • Validation: Ensure an appropriate error message is logged, and sensitive information is not exposed.

Missing Claims Test:

  • Input: A valid token missing critical claims like `role`.
  • Expected Output: `nil`.
  • Validation: Ensure the method flags the token as invalid due to missing data.

Tests for check_user_privileges

Sufficient Privileges Test:

  • Input: User info with `role` as `Admin` and a `required_privilege` of `Student`.
  • Expected Output: `true`.
  • Validation: Verify the method correctly interprets hierarchical roles.

Insufficient Privileges Test:

  • Input: User info with `role` as `Student` and a `required_privilege` of `Instructor`.
  • Expected Output: `false`.
  • Validation: Ensure the method denies access for insufficient privileges.

Missing Role Test:

  • Input: User info without a `role` claim.
  • Expected Output: `false`.
  • Validation: Ensure the method handles missing roles gracefully.

Tests for current_user_is_a?

Correct Role Test:

  • Input: Token with `role` as `Instructor` and `role_name` as `Instructor`.
  • Expected Output: `true`.
  • Validation: Verify the method returns `true` when roles match exactly.

Incorrect Role Test:

  • Input: Token with `role` as `TA` and `role_name` as `Instructor`.
  • Expected Output: `false`.
  • Validation: Ensure the method denies access when roles don’t match.

Missing Token Test:

  • Input: `nil` token.
  • Expected Output: `false`.
  • Validation: Confirm that the method handles missing tokens properly.

---

Integration Tests

Definition: Integration tests validate that the `AuthorizationHelper` module functions correctly within the context of the overall system.

Approach Options

1. Creating a Dummy API:

  • A lightweight API will be created specifically for testing the `AuthorizationHelper` methods.
  • This API will simulate requests requiring token authentication and privilege checks.
  • Advantages:
    • Fully isolated testing environment.
    • Flexibility to simulate different roles, tokens, and scenarios.
  • Disadvantages:
    • Requires additional setup and may not reflect real-world behavior perfectly.

2. Implementing a New Controller in Expertiza:

  • A controller will be added to the existing Expertiza application that uses the refactored `AuthorizationHelper`.
  • This ensures that tests occur in a real-world context, validating interactions with the actual system.
  • Advantages:
    • Validates the actual behavior of the module within the system.
    • Reflects real application workflows and edge cases.
  • Disadvantages:
    • Potentially affects other parts of the application during testing.
    • Requires effort to ensure test isolation.

Integration Test Cases

API Endpoint Authentication

  • Valid Token Test:
    • Scenario: Send a valid JWT token to a protected endpoint.
    • Expected Outcome: HTTP 200 OK with access granted.
    • Validation: Confirm the user is authenticated and receives the correct resource.
  • Expired Token Test:
    • Scenario: Send an expired JWT token to a protected endpoint.
    • Expected Outcome: HTTP 401 Unauthorized.
    • Validation: Ensure the system blocks access and provides a proper error message.
  • Tampered Token Test:
    • Scenario: Send a tampered token with an invalid signature.
    • Expected Outcome: HTTP 401 Unauthorized.
    • Validation: Confirm the system denies access and logs the attempt securely.
  • Missing Token Test:
    • Scenario: Make a request without providing a token.
    • Expected Outcome: HTTP 401 Unauthorized.
    • Validation: Verify that token authentication is strictly enforced.

Team

Mentor

  • Kashika Mallick (kmallick@ncsu.edu)

Members

  • Shafa Hassan (shassa22@ncsu.edu)
  • Archit Gupta (agupta85@ncsu.edu)
  • Ansh Ganatra (aganatr@ncsu.edu)