Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autolab config master cherrypick #2028

Merged
merged 20 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ 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
attachments/
doc/
storage/
out.txt

# compiled assets
Expand Down
7 changes: 4 additions & 3 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.4.10'
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 Expand Up @@ -170,4 +172,3 @@ gem 'lockbox'

# to decode / verify jwts for LTI Integration
gem "jwt"

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 @@ -1487,6 +1487,10 @@ table.sub td, th {
color: white !important;
}

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

.error-header {
color: $autolab-red;
}
30 changes: 30 additions & 0 deletions app/controllers/admins_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# this controller contains methods for system-wise
# admin functionality
class AdminsController < ApplicationController
rescue_from ActionView::MissingTemplate do |_exception|
redirect_to("/home/error_404")
end

skip_before_action :set_course

action_auth_level :email_instructors, :administrator
Expand Down Expand Up @@ -32,4 +36,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
12 changes: 6 additions & 6 deletions app/controllers/lti_launch_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ def validate_jwt_signature(id_token)
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)
end
Expand All @@ -204,13 +204,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 +238,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
Loading