CSC 630 Summer 2024 - Implementing Moodle Single Sign-on

From Expertiza_Wiki
Jump to navigation Jump to search

This wiki page is for the description of changes made under CSC 630 Summer 2024 - Implementing Moodle Single Sign-on

Team

Mentor

  • Ed Gehringer

Team Members

  • Ben Morris - bcmorri4@ncsu.edu

Expertiza Background

Expertiza is an open source Ruby on Rails application for teachers to manage classrooms. Expertiza is maintained by the teaching staff at NC State along with the very students who use it. Expertiza has many different models saved inside it including assignments, questions, and users. Relevant to our project, these models can be imported or exported through the website as CSV files for ease of uploading or editing many objects at once.

Description of project

My task was to implement single sign-on from Moodle to Expertiza. The idea is that an instructor can create a link to an Expertiza assignment within an assignment in Moodle. When the user clicks the Moodle link, they are brought to the Expertiza assignment without having to log in. If I had time, I was to implement deadlines in Expertiza showing up in the LMS calendar for that course and grades from Expertiza automatically showing up in the LMS gradebook. Unfortunately, I did not have time to get to these features as I will explain below.

Considerations

I considered two implementations: OAuth and LTI.

1. OAuth

OAuth (Open Authorization) is an open standard for access delegation commonly used to grant websites or applications limited access to user information without exposing passwords. NCSU's Learntech deemed it to be a security risk the way I needed to implement it.

2. LTI

LTI (Learning Tools Interoperability) is a standard developed by the IMS Global Learning Consortium to integrate educational applications with learning management systems (LMS). It enables seamless connectivity and data exchange between different educational tools and platforms, allowing for a cohesive learning experience. LTI allows tools to be launched from an LMS with user authentication and context information passed securely. This standard facilitates the integration of third-party learning applications without needing custom integrations for each LMS, promoting interoperability and scalability in educational technology.

LTI 1.1 (Learning Tools Interoperability 1.1) is an early version of the LTI standard that allows learning management systems (LMS) to integrate third-party tools. It supports the basic launch functionality, enabling tools to be accessed from within the LMS with user and course context information passed securely via an OAuth 1.0a protocol. However, LTI 1.1 has limited support for security and data privacy features compared to later versions.

LTI 1.3 (Learning Tools Interoperability 1.3) is a more advanced and secure version of the LTI standard, incorporating the latest industry standards for authentication and authorization. It utilizes OAuth 2.0 and JSON Web Tokens (JWT) for more robust security, providing enhanced data privacy and integrity. LTI 1.3 also includes support for deeper integration capabilities, such as enhanced user roles, resource linking, and rich communication between the LMS and external tools.

I chose LTI 1.1 for initial implementation with the idea that I would implement 1.3 given the time. I chose this route because implementation guides and resources are scarce, and the best material I found was for LTI 1.1. More information can be found at imsglobal.org.

Design

Facade Pattern: This pattern provides a simplified interface to a complex subsystem. In the case of LTI SSO, the LTI standard provides a simplified way for learning management systems (LMS) to interact with external tools. It hides the complexity of the authentication, authorization, and user context exchange processes.

Use

Moodle Admin

In order to implement this for use, the NCSU Moodle admin will need to add Expertiza as an external tool. To do this, the admin will follow these steps:

  1. Go to Site Administration -> Plugins -> External tool -> Manage tools -> configure a tool manually
  2. Tool name = Expertiza
  3. Tool URL = expertiza.ncsu.edu/lti/launch
  4. LTI version = LTI 1.0/1.1
  5. Consumer key = the value of LTI_CONSUMER_KEY in config/application.yml
  6. Shared secret = the value of LTI_SHARED_ENCODER in config/application.yml
  7. Tool configuration usage = Show in activity chooser and as a preconfigured tool
  8. Default launch container = New window
  9. Go to Privacy
  10. Share launcher's email with tool = Always

Admin video instructions

Moodle Instructor

Note: Moodle admin must add Expertiza as an external tool before an instructor can do this.

  1. Log in to your Moodle account
  2. Choose the course you want to add an assignment to
  3. Go to Edit mode
  4. Choose Add an activity or resource
  5. Choose Expertiza
  6. Fill it in as normal.
  7. When a student clicks on this assignment, if their Moodle email domain is "ncsu.edu" and their Moodle email prefix matches any username in Expertiza, they will be logged into Expertiza as that user and redirected to the general "Assignments page".

Instructor video instructions

Challenges

Initially, I was going to install both Expertiza and Moodle locally and once I got it to work, I would extend it to remote servers. I never could get Expertiza working locally, so I changed plans and installed Expertiza and Moodle on separate VCL images. I used the CSC 517 Expertiza development image for Expertiza and followed these instructions

  • Initially, I used the CSC 517 Expertiza development image to install Moodle as well. It uses CentOS 7 and there aren't a lot of resources for installing Moodle on CentOS, but I eventually got it working. After about a month, without having changed anything, the IP address to my Moodle server quit responding. I tried many things, including reinstalling the image and reinstalling Moodle, but I couldn't get it working. Eventually I realized that I wasn't restricted to the Expertiza image and created an Ubuntu 22.04 LTS image. I got it installed and could "connect" to Moodle, but not retrieve any files. I troubleshot and found the right firewall settings. I followed Moodle's install instructions and also added the following commands.
  1. sudo chown -R www-data:www-data /var/www/html
  2. sudo chmod -R 755 /var/www/html
  3. sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
  4. sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT

Current Status

lti_controller

Single sign-on: Expertiza will automatically log a student into their root assignments page *if* the email address passed by Moodle has "ncsu.edu" as the domain *and* the prefix of the email address matches a Unity ID within Expertiza. For example, if Moodle passes bcmorri4@ncsu.edu to Expertiza, Expertiza checks the domain and it passes and then checks to see if bcmorri4 is a current user. If bcmorri4 is a user, then it will log bcmorri4 in and redirect to the root assignments page.

Deadlines and course grades Not implemented. LTI 1.1 has something called "deep linking" that would allow these features. However, I would suggest implementing LTI 1.3 which is more secure and has more features for linking.

Final considerations

  • For security, consider moving LTI_CONSUMER_KEY and LTI_SHARED_ENCODER from config/application.yml to a secrets file that is also in .gitignore.
  • Consider changing the LTI_CONSUMER_KEY and LTI_SHARED_ENCODER in config/application.yml to different, random, secure 40-digit strings
  • Tested against actual Expertiza.ncsu.edu site. The test and production values of lti_base_url and moodle_base_url in config/application.yml might need to be changed.
  • Consider implementing LTI 1.3 in the future. More robust and secure.

Files modified

CHANGE THESE

  • Gemfile
  • Gemfile.lock
  • app/assets/javascripts/lti.coffee
  • app/assets/stylesheets/lti.scss
  • app/controllers/application_controller.rb
  • app/controllers/lti_controller.rb
  • app/helpers/lti_helper.rb
  • app/views/lti/launch.html.haml
  • config/application.rb
  • config/application.yml
  • config/environments/development.rb
  • config/initializers/headers.rb
  • config/initializers/lti.rb
  • config/puma.rb
  • config/routes.rb
  • db/schema.rb
  • public/lti_config.xml
  • spec/controllers/lti_controller_spec.rb
  • test/controllers/lti_controller_test.rb

lti_controller code

# app/controllers/lti_controller.rb

class LtiController < ApplicationController
  include AuthHelper # This gives access to AuthController methods

  protect_from_forgery with: :exception, except: [:launch]
  skip_before_action :authorize, only: [:launch]
  after_action :allow_iframe, only: [:launch]
  

  def launch
    begin
      authenticator = IMS::LTI::Services::MessageAuthenticator.new(
        request.url,
        request.request_parameters,
        ENV['LTI_SHARED_ENCODER']
      )

      # Check if the signature is valid
      if authenticator.valid_signature?
        # Retrieve user information from LTI parameters
        user_email = params['lis_person_contact_email_primary']
        username, domain = extract_user_and_domain_from_email_address(user_email)

        # Log the origin and referer
        origin = request.headers['Origin']

        if valid_user_domain?(domain) && valid_request_url?(origin)
          authenticate_and_login_user(username)
        else
          redirect_to root_path, alert: 'Invalid domain'
        end
      else
        redirect_to root_path, alert: 'Invalid LTI signature'
      end
    rescue => e
      Rails.logger.error "Error in LTI launch: #{e.message}"
      redirect_to root_path, alert: 'An error occurred during login'
    end
  end

  private

  def allow_iframe
    response.headers.except! 'X-Frame-Options'
  end

  def extract_user_and_domain_from_email_address(email)
    return [nil, nil] if email.blank?
    parts = email.split('@')
    parts.length == 2 ? parts : [nil, nil]
  end

  # Checks if user domain is "ncsu.edu" for authentication purposes
  def valid_user_domain?(domain)
    domain == "ncsu.edu"
  end

  # Checks that the website requesting the authentication is approved
  def valid_request_url?(url)
    url == ENV['LTI_TOOL_URL']
  end

  # Logs user in if they exist in Expertiza
  def authenticate_and_login_user(username)
    begin
      # Gets the user if they exist in Expertiza, else null
      user = User.find_by(name: username)
      if user
        # Log the user in
        session[:user] = user  # Store the entire user object, not just the username
        AuthController.set_current_role(user.role_id, session)
        ExpertizaLogger.info LoggerMessage.new('', user.name, 'Login successful via LTI')
        redirect_to "#{ENV['LTI_BASE_URL']}/student_task/list", notice: 'Logged in successfully via LTI'
      else
        redirect_to root_path, alert: 'User not found in Expertiza. Please register first.'
      end
    rescue => e
      Rails.logger.error "Error in LTI launch.authenticate_and_login: #{e.message}"
      redirect_to root_path, alert: 'An error occurred during login'
    end
  end
end

application.yml code

# config/application.yml

development:
  expertiza_base_url: "http://152.7.177.3:8080"
  moodle_base_url: "http://152.7.177.192"
  LTI_CONSUMER_KEY: "123456"
  LTI_SHARED_ENCODER: "123456"
  LTI_TOOL_URL: "http://152.7.177.192"

test:
  expertiza_base_url: "http://152.7.177.58"
  moodle_base_url: "http://152.7.177.192"
  LTI_CONSUMER_KEY: "123456"
  LTI_SHARED_ENCODER: "123456"
  LTI_TOOL_URL: "http://152.7.177.192"
  
production:
  expertiza_base_url: "http://expertiza.ncsu.edu/"
  moodle_base_url: "http://152.7.177.192"
  LTI_CONSUMER_KEY: "123456"
  LTI_SHARED_ENCODER: "123456"
  LTI_TOOL_URL: "http://152.7.177.192"
  # LTI_TOOL_URL: "https://wolfware.ncsu.edu"

Test Plan

Black box

  1. In testing functionality, I followed the install steps in section 6.
  2. Created a test user in Expertiza
  3. Created a test class in Moodle
  4. Created the following test users in Moodle:
    1. valid Expertiza user id and invalid domain
    2. invalid Expertiza user id and valid domain
    3. valid Expertiza user id and valid domain
    4. valid Expertiza instructor id and valid domain
  5. Tested opening the assignment in each of the Moodle user's accounts
  6. Verified expected behavior occurred:
    1. Users 1 and 2 were not logged in and were redirected to the login page.
    2. Users 3 and 4 were logged in and redirected to their assignments page.

rspec

For the lti_controller class, I tested the following cases:

  • Public methods
    • Valid LTI signature and valid domain
      • Authenticates and logs in the user
      • Redirects to the student task list
    • Valid LTI signature and invalid domain
      • Redirects to root path with an invalid domain alert
    • Error occurs
      • Logs the error and redirects to root path with an error alert
  • Private methods
    • allow_iframe
      • removes X-Frame-Options from the response headers
    • xtract_user_and_domain_from_email_address
      • returns username and domain when email is valid
      • returns nils when email is nil or empty
      • returns nils when email format is invalid
    • valid_user_domain?
      • returns true for valid domain
      • returns false for invalid domain
    • valid_tool_url?
      • returns true for valid tool url
      • returns false for invalid tool url
      • returns false for nil tool url
    • authenticate_and_login_user
      • When user exists, it sets the session user and redirects to student task list
      • When user does not exist, it redirects to root path with an alert
      • When an error occurs, it logs the error and redirects to root path with an alert

Execution of Test Plan

I achieved 100% coverage of lti_controller via Rspec.

To run the test cases, use the command rspec spec/controllers/lti_controller_spec.rb

Video of coverage

Testing needed

I was unable to test this in a production setting. All tests were implemented using VCL images and servers. The code needs to be tested in the actual expertiza.ncsu.edu codebase. This could be done with a VCL Moodle server first. When that is working, then NCSU Moodle admin can add Expertiza as an external tool using the directions above and test it in a full production scenario.

Pull Request URL

Ben Morris CSC 630 Summer Project: Implementing single sign-on using LTI 1.1