Skip to content

Commit

Permalink
Autolab config master cherrypick (#2028)
Browse files Browse the repository at this point in the history
* Create Autolab configure tabs (#1825)

* Create Autolab configure tabs

* Delete lti_tool_jwk.json

* Delete lti_platform_jwk.json

* addressed autolab_config spec nit

---------

Co-authored-by: Nicholas Clark <[email protected]>
Co-authored-by: Victor Huang <[email protected]>

* Google OAuth Config UI (#1870)

* Oauth config page UI

* OAuth UI config complete

* Addressed nit

---------

Co-authored-by: Nicholas Clark <[email protected]>

* OAuth flow fix (#1892)

* Oauth config page UI

* OAuth UI config complete

* Addressed nit

* Overhauled oauth workflow

* Fixed new user registration flow

* Fixed issue with user confirmation not being overridden by OAuth

* Fixed OAuth button on hover color

---------

Co-authored-by: Nicholas Clark <[email protected]>

* Autolab config UI smtp (#1839)

* Create Autolab configure tabs

* SMTP config page

* SMTP config page implementation complete

* Delete lti_platform_jwk.json

* Delete lti_tool_jwk.json

* Added default_from field to smtp config settings page

* Moved default from input to seperate section

---------

Co-authored-by: Nicholas Clark <[email protected]>

* Add GitHub Configuration Tab to Configure Autolab (#1843)

* Adds github integration config form

* Add GitHub Configuration Page

* Update Github integration documentation

* Display current github config

* Fixed cherrypick issues

* Update Gemfile.lock

* Run Rubocop

* Undo changes, fix tests

* fix admin controller tests

* fix lti config controller tests

* Remove redundant titles

* Remove redundant tags, make file extension of `_lti_integration.html.erb` consistent

* Add missing raises

* Remove rescue_from

* Remove extraneous tmpfile param

---------

Co-authored-by: Nicholas Clark <[email protected]>
Co-authored-by: Victor Huang <[email protected]>
Co-authored-by: Damian Ho <[email protected]>
Co-authored-by: Joey Wildman <[email protected]>
  • Loading branch information
5 people authored Apr 2, 2024
1 parent 10c2fd9 commit ce0533d
Show file tree
Hide file tree
Showing 38 changed files with 797 additions and 263 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ coverage/
config/database.yml
config/school.yml
config/lti_config.yml
config/lti_tool_jwk.json
config/lti_platform_jwk.json
config/lti_tool_jwk.json
config/smtp_config.yml
config/github_config.yml
config/oauth_config.yml

# Autolab user documents
app/views/home/_topannounce.html.erb
Expand Down
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ gem 'terser', '>= 1.1.7'
gem 'coffee-rails', '>= 4.0.0'

# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'mini_racer', '~> 0.6.3' , platforms: :ruby
gem 'mini_racer', '~> 0.6.3', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
Expand All @@ -45,8 +45,8 @@ gem 'slack-notifier'
gem 'exception_notification', ">= 4.1.0"

# Used by lib/tasks/autolab.rake to populate DB with dummy seed data
gem 'populator', '>=1.0.0'
gem 'rake', '>=10.3.2'
gem 'populator', '>=1.0.0'

# To communicate with MySQL database
gem 'mysql2', '~>0.5'
Expand All @@ -59,6 +59,8 @@ gem 'devise', '>=4.5.0'
gem 'omniauth', '>=1.2.2'
gem 'omniauth-facebook', '>=2.0.0'
gem 'omniauth-google-oauth2', '>=0.2.5'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
gem 'omniauth-shibboleth', '>=1.1.2'
gem 'omniauth-shibboleth-redux', '~> 2.0', require: 'omniauth-shibboleth'

# OAuth2 authentication
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ GEM
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth-shibboleth-redux (2.0.0)
omniauth (>= 2.0.0)
orm_adapter (0.5.0)
Expand Down Expand Up @@ -512,6 +517,8 @@ DEPENDENCIES
omniauth (>= 1.2.2)
omniauth-facebook (>= 2.0.0)
omniauth-google-oauth2 (>= 0.2.5)
omniauth-rails_csrf_protection (~> 1.0)
omniauth-shibboleth (>= 1.1.2)
omniauth-shibboleth-redux (~> 2.0)
overcommit
populator (>= 1.0.0)
Expand Down
4 changes: 4 additions & 0 deletions app/assets/javascripts/autolab_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
document.addEventListener('DOMContentLoaded', function () {
$('#config-tabs').tabs();
$('.collapsible').collapsible();
});
4 changes: 4 additions & 0 deletions app/assets/stylesheets/style.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,10 @@ table.sub td, th {
color: white !important;
}

.oauth-btn:hover {
background-color: $autolab-subtle-gray !important;
}

.error-header {
color: $autolab-red;
}
26 changes: 26 additions & 0 deletions app/controllers/admins_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,30 @@ def clear_cache
flash[:success] = "Cache Cleared"
redirect_back(fallback_location: root_path)
end

action_auth_level :autolab_config, :administrator
def autolab_config
@github_integration = GithubIntegration.check_github_authorization

if File.exist?("#{Rails.configuration.config_location}/lti_config.yml")
@lti_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.config_location}/lti_config.yml"))
end

if Rails.cache.exist?(:tmp_smtp_config)
@smtp_config_hash = Rails.cache.read(:tmp_smtp_config)
Rails.cache.delete(:tmp_smtp_config)
elsif File.exist?("#{Rails.configuration.config_location}/smtp_config.yml")
@smtp_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.config_location}/smtp_config.yml"))
@smtp_config_hash.symbolize_keys!
end

@github_config_hash = {
client_id: Rails.configuration&.x&.github&.client_id || "",
client_secret: Rails.configuration&.x&.github&.client_secret || ""
}

@configured_oauth_providers = OauthConfigController.get_oauth_providers
end
end
28 changes: 28 additions & 0 deletions app/controllers/github_config_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class GithubConfigController < ApplicationController
skip_before_action :set_course
skip_before_action :authorize_user_for_course
skip_before_action :update_persistent_announcements

action_auth_level :update_config, :administrator
def update_config
required_params = %w[client_id client_secret]
required_params.each do |param|
if params[param].blank?
flash[:error] = "#{param} field was missing"
redirect_to(autolab_config_admin_path(active: :github)) && return
end
end

Rails.configuration.x.github.client_id = params[:client_id]
Rails.configuration.x.github.client_secret = params[:client_secret]

yaml_hash = { github: { client_id: params[:client_id],
client_secret: params[:client_secret] } }
File.open("#{Rails.configuration.config_location}/github_config.yml", "w") do |file|
file.write(YAML.dump(yaml_hash.deep_stringify_keys))
end

flash[:success] = "Github configuration was successfully updated"
redirect_to autolab_config_admin_path(active: :github)
end
end
23 changes: 8 additions & 15 deletions app/controllers/lti_config_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,13 @@ class LtiConfigController < ApplicationController
skip_before_action :authorize_user_for_course
skip_before_action :update_persistent_announcements

action_auth_level :index, :administrator
def index
return unless File.exist?("#{Rails.configuration.lti_config_location}/lti_config.yml")

@lti_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.lti_config_location}/lti_config.yml"))
end
action_auth_level :update_config, :administrator
def update_config
required_params = %w[iss developer_key auth_url oauth2_access_token_url]
required_params.each do |param|
if params[param].blank?
flash[:error] = "#{param} field was missing"
redirect_to(lti_config_index_path) && return
redirect_to(autolab_config_admin_path(active: :lti)) && return
end
end

Expand All @@ -32,36 +25,36 @@ def update_config
}
uploaded_tool_jwk_file = params['tool_jwk']
# Ensure user uploaded private JWK for config, or it already exists
if !File.exist?("#{Rails.configuration.lti_config_location}/lti_tool_jwk.json") &&
if !File.exist?("#{Rails.configuration.config_location}/lti_tool_jwk.json") &&
uploaded_tool_jwk_file.nil?
flash[:error] = "No tool JWK JSON file was uploaded"
redirect_to(lti_config_index_path) && return
redirect_to(autolab_config_admin_path(active: :lti)) && return
end
# Ensure either plaform has a jwk file associated with it or URL to public JWKs
uploaded_platform_public_jwk_file = params['platform_public_jwk_json']
if uploaded_platform_public_jwk_file.nil? && yaml_hash[:platform_public_jwks_url].blank?
flash[:error] =
"No platform JWK JSON file or URL was uploaded. Please specify one or the other"
redirect_to(lti_config_index_path) && return
redirect_to(autolab_config_admin_path(active: :lti)) && return
end
# write text parameters to config yml
File.open("#{Rails.configuration.lti_config_location}/lti_config.yml", "w") do |file|
File.open("#{Rails.configuration.config_location}/lti_config.yml", "w") do |file|
file.write(YAML.dump(yaml_hash.deep_stringify_keys))
end
# write private key to separate file if it was uploaded
unless uploaded_tool_jwk_file.nil?
File.open("#{Rails.configuration.lti_config_location}/lti_tool_jwk.json", "w") do |file|
File.open("#{Rails.configuration.config_location}/lti_tool_jwk.json", "w") do |file|
file.write(uploaded_tool_jwk_file.read)
end
end
# write platform public key to separate file, if it exists
unless uploaded_platform_public_jwk_file.nil?
File.open("#{Rails.configuration.lti_config_location}/lti_platform_jwk.json", "w") do |file|
File.open("#{Rails.configuration.config_location}/lti_platform_jwk.json", "w") do |file|
file.write(uploaded_platform_public_jwk_file.read)
end
end

flash[:success] = "LTI configuration was successfully updated"
redirect_to(lti_config_index_path) && return
redirect_to(autolab_config_admin_path(active: :lti)) && return
end
end
18 changes: 10 additions & 8 deletions app/controllers/lti_launch_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,17 @@ def validate_jwt_signature(id_token)
# make a GET request to public JWK endpoint
response = conn.get("")
if response.body["keys"].nil?
LtiError.new("No keys were found from public JWK url", :internal_server_error)
raise LtiError.new("No keys were found from public JWK url", :internal_server_error)
end

platform_public_jwks = JSON.parse(response.body)["keys"]
elsif File.exist?("#{Rails.configuration.lti_config_location}/lti_platform_jwk.json")
elsif File.exist?("#{Rails.configuration.config_location}/lti_platform_jwk.json")
# static platform public key, so take key from yml
platform_public_key_file =
File.read("#{Rails.configuration.lti_config_location}/lti_platform_jwk.json")
File.read("#{Rails.configuration.config_location}/lti_platform_jwk.json")
else
LtiError.new("No platform public key or public JWK url provided", :internal_server_error)
raise LtiError.new("No platform public key or public JWK url provided",
:internal_server_error)
end
rsa_public_key = get_public_key(platform_public_key_file, platform_public_jwks)
if rsa_public_key.nil?
Expand All @@ -204,13 +206,13 @@ def validate_jwt_signature(id_token)
def launch
# Code based on:
# https://github.com/IMSGlobal/lti-1-3-php-library/blob/master/src/lti/LTI_Message_Launch.php
unless File.exist?("#{Rails.configuration.lti_config_location}/lti_config.yml")
unless File.exist?("#{Rails.configuration.config_location}/lti_config.yml")
raise LtiError.new("LTI configuration not found on Autolab Server", :internal_server_error)
end

# load LTI configuration from file
@lti_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.lti_config_location}/lti_config.yml"))
YAML.safe_load(File.read("#{Rails.configuration.config_location}/lti_config.yml"))

@user = current_user
validate_state(params)
Expand Down Expand Up @@ -238,13 +240,13 @@ def launch
# build our authentication response and redirect back to
# platform
def oidc_login
unless File.exist?("#{Rails.configuration.lti_config_location}/lti_config.yml")
unless File.exist?("#{Rails.configuration.config_location}/lti_config.yml")
raise LtiError.new("LTI configuration not found on Autolab Server", :internal_server_error)
end

# load LTI configuration from file
@lti_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.lti_config_location}/lti_config.yml"))
YAML.safe_load(File.read("#{Rails.configuration.config_location}/lti_config.yml"))

# code based on: https://github.com/IMSGlobal/lti-1-3-php-library/blob/master/src/lti/LTI_OIDC_Login.php
# validate OIDC
Expand Down
8 changes: 4 additions & 4 deletions app/controllers/lti_nrps_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def respond_with_lti_error(error)
action_auth_level :request_access_token, :instructor
def request_access_token
# get private key from JSON file to sign Autolab's client assertion as a JWK
unless File.exist?("#{Rails.configuration.lti_config_location}/lti_tool_jwk.json")
unless File.exist?("#{Rails.configuration.config_location}/lti_tool_jwk.json")
flash[:error] = "Autolab's JWK JSON file was not found"
redirect_to([:users, @course]) && return
end

jwk_json = File.read("#{Rails.configuration.lti_config_location}/lti_tool_jwk.json")
jwk_json = File.read("#{Rails.configuration.config_location}/lti_tool_jwk.json")
begin
jwk_hash = JSON.parse(jwk_json)
rescue JSON::ParserError => e
Expand All @@ -28,7 +28,7 @@ def request_access_token
end
# load LTI configuration from file
lti_config_hash =
YAML.safe_load(File.read("#{Rails.configuration.lti_config_location}/lti_config.yml"))
YAML.safe_load(File.read("#{Rails.configuration.config_location}/lti_config.yml"))

if jwk_hash['kid'].blank? || jwk_hash['alg'].blank?
flash[:error] = "Autolab's JWK JSON file does not contain kid or alg"
Expand Down Expand Up @@ -97,7 +97,7 @@ def sync_roster
@lti_context_membership_url = lcd.membership_url
@course = lcd.course

unless File.exist?("#{Rails.configuration.lti_config_location}/lti_config.yml")
unless File.exist?("#{Rails.configuration.config_location}/lti_config.yml")
flash[:error] = "Could not find LTI Configuration"
redirect_to([:users, @course]) && return
end
Expand Down
73 changes: 73 additions & 0 deletions app/controllers/oauth_config_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'action_mailer'
require 'tempfile'
require 'psych'

class OauthConfigController < ApplicationController
skip_before_action :set_course
skip_before_action :authorize_user_for_course
skip_before_action :update_persistent_announcements

action_auth_level :update_oauth_config, :administrator
def update_oauth_config
required_params = %w[provider client_id client_secret]
required_params.each do |param|
if params[param].blank?
flash[:error] = "#{param} field was missing"
redirect_to(autolab_config_admin_path(active: :oauth)) && return
end
end

yaml_hash = {}
if File.exist?("#{Rails.configuration.config_location}/oauth_config.yml")
yaml_hash = YAML.safe_load(
File.read("#{Rails.configuration.config_location}/oauth_config.yml")
)
end

yaml_hash[params[:provider]] = {
client_id: params[:client_id],
client_secret: params[:client_secret]
}

Rails.cache.write(:oauth_config, yaml_hash.deep_symbolize_keys!)

File.open("#{Rails.configuration.config_location}/oauth_config.yml", "w") do |file|
file.write(YAML.dump(yaml_hash.deep_stringify_keys))
end

flash[:success] = "OAuth Config successfully updated"

redirect_to autolab_config_admin_path(active: :oauth)
end

def self.get_oauth_providers
Rails.cache.fetch(:oauth_providers) do
return [] unless File.exist?("#{Rails.configuration.config_location}/oauth_config.yml")

config_hash = YAML.safe_load(
File.read("#{Rails.configuration.config_location}/oauth_config.yml")
).deep_symbolize_keys!

providers = []
config_hash.each do |provider, _|
providers.append provider
end

providers
end
end

def self.get_oauth_credentials(provider)
oauth_config = Rails.cache.fetch(:oauth_config) do
return {} unless File.exist?("#{Rails.configuration.config_location}/oauth_config.yml")

YAML.safe_load(
File.read("#{Rails.configuration.config_location}/oauth_config.yml")
).deep_symbolize_keys!
end

return {} unless oauth_config.key? provider

oauth_config[provider]
end
end
Loading

0 comments on commit ce0533d

Please sign in to comment.