201 lines
5.9 KiB
Ruby
201 lines
5.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ApplicationController < ActionController::Base
|
|
include ApplicationHelper
|
|
include Pundit::Authorization
|
|
|
|
MEMBER_ACTIONS = %i[destroy edit show update].freeze
|
|
RENDER_HOST = CodeOcean::Config.new(:code_ocean).read[:render_host]
|
|
LEGAL_SETTINGS = CodeOcean::Config.new(:code_ocean).read[:legal] || {}
|
|
MONITORING_USER_AGENT = /updown\.io/
|
|
|
|
before_action :deny_access_from_render_host
|
|
after_action :verify_authorized, except: %i[welcome]
|
|
around_action :mnemosyne_trace
|
|
around_action :switch_locale
|
|
before_action :set_sentry_context, :load_embed_options, :set_document_policy
|
|
protect_from_forgery(with: :exception, prepend: true)
|
|
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
|
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
|
rescue_from ActionController::InvalidAuthenticityToken, with: :render_csrf_error
|
|
add_flash_types :danger, :warning, :info, :success
|
|
|
|
def current_user
|
|
@current_user ||= find_or_login_current_user&.store_current_study_group_id(session[:study_group_id])
|
|
end
|
|
|
|
def current_contributor
|
|
@current_contributor ||= if session[:pg_id]
|
|
current_user.programming_groups.find(session[:pg_id])
|
|
else
|
|
current_user
|
|
end
|
|
end
|
|
helper_method :current_contributor
|
|
|
|
def welcome
|
|
# Show root page
|
|
redirect_to ping_index_path if MONITORING_USER_AGENT.match?(request.user_agent)
|
|
end
|
|
|
|
private
|
|
|
|
def require_user!
|
|
raise Pundit::NotAuthorizedError unless current_user
|
|
end
|
|
|
|
def deny_access_from_render_host
|
|
raise Pundit::NotAuthorizedError if RENDER_HOST.present? && request.host == RENDER_HOST
|
|
end
|
|
|
|
def load_embed_options
|
|
@embed_options = if session[:embed_options].present? && session[:embed_options].is_a?(Hash)
|
|
session[:embed_options].symbolize_keys
|
|
else
|
|
{}
|
|
end
|
|
Sentry.set_extras(@embed_options)
|
|
@embed_options
|
|
end
|
|
|
|
def find_or_login_current_user
|
|
login_from_authentication_token ||
|
|
login_from_lti_session ||
|
|
login_from_session ||
|
|
login_from_other_sources ||
|
|
nil
|
|
end
|
|
|
|
def login_from_lti_session
|
|
return unless session[:external_user_id]
|
|
|
|
ExternalUser.find_by(id: session[:external_user_id])
|
|
end
|
|
|
|
def login_from_authentication_token
|
|
return unless params[:token]
|
|
|
|
token = AuthenticationToken.find_by(shared_secret: params[:token])
|
|
return unless token
|
|
|
|
if token.expire_at.future?
|
|
token.update(expire_at: Time.zone.now)
|
|
session[:study_group_id] = token.study_group_id
|
|
|
|
# Sorcery Login only works for InternalUsers
|
|
return auto_login(token.user) if token.user.is_a? InternalUser
|
|
|
|
# All external users are logged in "manually"
|
|
session[:external_user_id] = token.user.id
|
|
token.user
|
|
end
|
|
end
|
|
|
|
def set_sentry_context
|
|
return if current_user.blank?
|
|
|
|
Sentry.set_user(
|
|
id: current_user.id,
|
|
type: current_user.class.name,
|
|
consumer: current_user.consumer&.name
|
|
)
|
|
end
|
|
|
|
def set_document_policy
|
|
# Instruct browsers to capture profiling data
|
|
response.set_header('Document-Policy', 'js-profiling')
|
|
end
|
|
|
|
def render_csrf_error
|
|
render_error t('sessions.expired'), :unprocessable_content
|
|
end
|
|
|
|
def render_not_authorized
|
|
render_error t('application.not_authorized'), :unauthorized
|
|
end
|
|
|
|
def render_not_found
|
|
if current_user&.admin?
|
|
render_error t('application.not_found'), :not_found
|
|
else
|
|
render_not_authorized
|
|
end
|
|
end
|
|
|
|
def render_error(message, status)
|
|
set_sentry_context
|
|
respond_to do |format|
|
|
format.any do
|
|
# Prevent redirect loop
|
|
if request.url == request.referer || request.referer&.match?(sign_in_path)
|
|
redirect_to :root, alert: message
|
|
# Redirect to main domain if the request originated from our render_host
|
|
elsif request.path == '/' && request.host == RENDER_HOST
|
|
redirect_to Rails.application.config.action_mailer.default_url_options, allow_other_host: true
|
|
elsif current_user.nil? && status == :unauthorized
|
|
session[:return_to_url] = request.fullpath if current_user.nil?
|
|
redirect_to sign_in_path, alert: t('application.not_signed_in')
|
|
else
|
|
redirect_back fallback_location: :root, allow_other_host: false, alert: message
|
|
end
|
|
end
|
|
format.json { render json: {error: message}, status: }
|
|
end
|
|
end
|
|
|
|
def mnemosyne_trace
|
|
yield
|
|
ensure
|
|
if ::Mnemosyne::Instrumenter.current_trace.present?
|
|
::Mnemosyne::Instrumenter.current_trace.meta['session_id'] = session[:session_id]
|
|
::Mnemosyne::Instrumenter.current_trace.meta['csrf_token'] = session[:_csrf_token]
|
|
::Mnemosyne::Instrumenter.current_trace.meta['external_user_id'] = session[:external_user_id]
|
|
end
|
|
end
|
|
|
|
def set_content_type_nosniff
|
|
# When sending a file, we want to ensure that browsers follow our Content-Type header
|
|
response.headers['X-Content-Type-Options'] = 'nosniff'
|
|
end
|
|
|
|
def sanitized_lti_locale_param
|
|
sanitize_locale(params[:custom_locale])
|
|
end
|
|
|
|
def sanitized_locale_param
|
|
sanitize_locale(params[:locale])
|
|
end
|
|
|
|
def sanitized_session_locale
|
|
sanitize_locale(session[:locale])
|
|
end
|
|
|
|
def choose_locale
|
|
sanitized_lti_locale_param ||
|
|
sanitized_locale_param ||
|
|
sanitized_session_locale ||
|
|
http_accept_language.compatible_language_from(I18n.available_locales) ||
|
|
I18n.default_locale
|
|
end
|
|
|
|
def switch_locale(&)
|
|
locale = choose_locale
|
|
session[:locale] = locale
|
|
Sentry.set_extras(locale:)
|
|
I18n.with_locale(locale, &)
|
|
end
|
|
|
|
# Sanitize given locale.
|
|
#
|
|
# Return `nil` if the locale is blank or not available.
|
|
#
|
|
def sanitize_locale(locale)
|
|
return if locale.blank?
|
|
|
|
locale = locale.downcase.to_sym
|
|
return unless I18n.available_locales.include?(locale)
|
|
|
|
locale
|
|
end
|
|
end
|