app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include ApplicationShared
include Foreman::Controller::Flash
include Foreman::Controller::Authorize
protect_from_forgery with: :exception # See ActionController::RequestForgeryProtection for details
rescue_from Exception, :with => :generic_exception if Rails.env.production?
rescue_from ScopedSearch::QueryNotSupported, :with => :invalid_search_query
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
rescue_from ProxyAPI::ProxyException, :with => :smart_proxy_exception
rescue_from Foreman::MaintenanceException, :with => :service_unavailable
rescue_from ActiveRecord::SubclassNotFound, :with => :sti_clean_up
# standard layout to all controllers
helper 'layout'
helper_method :resource_path
before_action :load_settings
before_action :require_login, :check_user_enabled
before_action :set_gettext_locale_db, :set_gettext_locale
before_action :session_expiry, :update_activity_time, :unless => proc { |c| c.remote_user_provided? || c.api_request? }
before_action :set_taxonomy, :require_mail, :check_empty_taxonomy
before_action :authorize
before_action :welcome, :find_selected_columns, :only => :index, :unless => :api_request?
around_action :set_timezone
attr_reader :original_search_parameter
def welcome
return if model_of_controller&.any?
if template_exists?(:welcome, _prefixes, variants: request.variant)
@welcome = true
render :welcome
end
end
def api_request?
request.format.try(:json?) || request.format.try(:yaml?)
end
# this method is returns the active user which gets used to populate the audits table
def current_user
User.current
end
def resource_path(type)
return '' if type.nil?
path = type.pluralize.underscore + "_path"
prefix, suffix = path.split('/', 2)
if path.include?("/") && Rails.application.routes.mounted_helpers.method_defined?(prefix)
# handle mounted engines
engine = send(prefix)
engine.send(suffix) if engine.respond_to?(suffix)
else
path = path.tr("/", "_")
send(path) if respond_to?(path)
end
end
protected
# Authorize the user for the requested action
def authorize
unless User.current.present?
render :json => { :error => "Authentication error" }, :status => :unauthorized
return
end
authorized ? true : deny_access
end
def deny_access
(User.current.logged? || request.xhr?) ? render_403 : require_login
end
# This filter is called before FastGettext set_gettext_locale and sets user-defined locale
# from db. It must be called after require_login.
def set_gettext_locale_db
params[:locale] ||= User.current.try(:locale)
end
def require_mail
user = User.current
if user && !user.hidden? && user.mail_enabled && user.mail.blank?
msg = _("An email address is required, please update your account details")
respond_to do |format|
format.html do
error msg
flash.keep # keep any warnings added by the user login process, they may explain why this occurred
redirect_to main_app.edit_user_path(:id => user)
end
format.text do
render :plain => msg, :status => :unprocessable_entity, :content_type => Mime[:text]
end
end
true
end
end
def invalid_request
render :plain => _('Invalid query'), :status => :bad_request
end
def not_found(exception = nil)
logger.debug "not found: #{exception}" if exception
respond_to do |format|
format.html { render "common/404", :status => :not_found }
format.any { head :not_found }
end
true
end
def service_unavailable(exception = nil)
logger.debug "service unavailable: #{exception}" if exception
respond_to do |format|
format.html { render "common/503", :status => :service_unavailable, :locals => { :exception => exception } }
format.any { head :service_unavailable }
end
true
end
def sti_clean_up(e)
require_login rescue false
Foreman::Logging.exception("Action failed", e) unless User.current&.admin?
@unknown_class_name = e.message.match(/subclass: '(.*)'\./)[1]
@parent_class = e.message.match(/overwrite (.*)\.inheritance_column/)[1].constantize
if params[:confirm_data_deletion] == 'yes' && User.current&.admin?
begin
@parent_class.where(@parent_class.inheritance_column => @unknown_class_name).delete_all
rescue ActiveRecord::InvalidForeignKey => e
Foreman::Logging.exception("Error during STI data cleanup", e)
render 'common/class_clean_up_failed'
return
end
flash[:success] = _('Data has been cleaned up')
redirect_back fallback_location: root_path
else
render 'common/confirm_class_clean_up'
end
end
def smart_proxy_exception(exception = nil)
Foreman::Logging.exception("ProxyAPI operation FAILED", exception)
if request.headers.include? 'HTTP_REFERER'
process_error(:redirect => :back, :error_msg => exception.message)
else
process_error(:render => { :plain => exception.message },
:error_msg => exception.message)
end
end
# this method sets the Current user to be the Admin
# its required for actions which are not authenticated by default
# such as unattended notifications coming from an OS, or fact and reports creations
def set_admin_user
User.current = User.anonymous_api_admin
end
def model_of_controller
@model_of_controller ||= controller_path.singularize.camelize.gsub('/', '::').safe_constantize
end
def controller_permission
controller_name
end
def action_permission
case params[:action]
when 'new', 'create'
'create'
when 'edit', 'update'
'edit'
when 'destroy'
'destroy'
when 'index', 'show'
'view'
else
raise ::Foreman::Exception.new(N_("unknown permission for %s"), "#{params[:controller]}##{params[:action]}")
end
end
# Not all models include Authorizable so we detect whether we should apply authorized scope or not
def resource_base
@resource_base ||= if model_of_controller.respond_to?(:authorized)
model_of_controller.authorized(current_permission)
else
model_of_controller.all
end
end
# this method is used with nested resources, where obj_id is passed into the parameters hash.
# it automatically updates the search text box with the relevant relationship
# e.g. /hosts/fqdn/reports # would add host = fqdn to the search bar
def setup_search_options
@original_search_parameter = params[:search]
params[:search] ||= ""
params.each do |param, value|
if param =~ /(\w+)_id$/ && value.present?
query = "#{Regexp.last_match(1)} = #{value}"
unless params[:search].include? query
params[:search] += ' and ' if params[:search].present?
params[:search] += query
end
end
end
end
# returns current SSO method object according to session
# nil is returned if nothing was found or invalid method is stored
def get_sso_method
if (sso_method_class = session[:sso_method])
sso_method_class.constantize.new(self)
end
rescue NameError
logger.error "Unknown SSO method #{sso_method_class}"
nil
end
def ajax?
request.xhr?
end
def ajax_request
return head(:method_not_allowed) unless ajax?
end
def remote_user_provided?
return false unless Setting["authorize_login_delegation"]
return false if api_request? && !(Setting["authorize_login_delegation_api"])
(@remote_user = request.env["HTTP_REMOTE_USER"]).present?
end
def resource_base_with_search
resource_base.search_for(params[:search], :order => params[:order])
end
def resource_base_search_and_page(tables = [])
base = tables.empty? ? resource_base_with_search : resource_base_with_search.eager_load(*tables)
base.paginate(:page => params[:page], :per_page => params[:per_page])
end
private
def require_admin
unless User.current.admin?
render_403(_('Administrator user account required'))
return false
end
true
end
def render_403(msg = nil)
if msg.nil?
@missing_permissions = Foreman::AccessControl.permissions_for_controller_action(path_to_authenticate)
Foreman::Logging.logger('permissions').info "rendering 403 because of missing permission #{@missing_permissions.map(&:name).join(', ')}"
else
@missing_permissions = []
Foreman::Logging.logger('permissions').info msg
end
respond_to do |format|
format.html { render :template => "common/403", :layout => !ajax?, :status => :forbidden }
format.any { head :forbidden }
end
false
end
# this is only used in hosts_controller (by SmartProxyAuth module) to render 403's
def render_error(msg, status)
render_403(msg)
end
def process_success(hash = {})
hash[:object] ||= instance_variable_get("@#{controller_name.singularize}")
hash[:object_name] ||= hash[:object].to_s
unless hash[:success_msg]
hash[:success_msg] = case action_name
when "create"
_("Successfully created %s.") % hash[:object_name]
when "update"
_("Successfully updated %s.") % hash[:object_name]
when "destroy"
_("Successfully deleted %s.") % hash[:object_name]
else
raise Foreman::Exception.new(N_("Unknown action name for success message: %s"), action_name)
end
end
hash[:success_redirect] ||= saved_redirect_url_or(send("#{controller_name}_url"))
success hash[:success_msg]
if hash[:success_redirect] == :back
redirect_back(fallback_location: saved_redirect_url_or(send("#{controller_name}_url")))
else
redirect_to hash[:success_redirect]
end
end
def process_error(hash = {})
hash[:object] ||= instance_variable_get("@#{controller_name.singularize}")
if hash[:render].blank? && hash[:redirect].blank?
case action_name
when "create" then hash[:render] = "new"
when "update" then hash[:render] = "edit"
else
hash[:redirect] = send("#{controller_name}_url")
end
end
logger.error "Failed to save: #{hash[:object].errors.full_messages.join(', ')}" if hash[:object].respond_to?(:errors)
hash[:error_msg] ||= [hash[:object].errors[:base] + hash[:object].errors[:conflict].map { |e| _("Conflict - %s") % e }].flatten
hash[:error_msg] = [hash[:error_msg]].flatten.to_sentence
if hash[:render]
error(hash[:error_msg], true) unless hash[:error_msg].empty?
render hash[:render]
elsif hash[:redirect]
error(hash[:error_msg]) unless hash[:error_msg].empty?
if hash[:redirect] == :back
redirect_back(fallback_location: send("#{controller_name}_url"))
else
redirect_to hash[:redirect]
end
end
end
def process_ajax_error(exception, action = nil)
action ||= action_name
origin = exception.original_exception if exception.present? && exception.respond_to?(:original_exception)
Foreman::Logging.exception("Failed to #{action}", exception)
Foreman::Logging.exception("Originally caused by", origin) if origin
message = (origin || exception).message
render :partial => "common/ajax_error", :status => :internal_server_error, :locals => { :message => message }
end
def redirect_back_or_to(url)
redirect_back(fallback_location: url, allow_other_host: false)
end
def saved_redirect_url_or(default)
session["redirect_to_url_#{controller_name}"] || default
end
def generic_exception(exception)
if exception.try(:cause).is_a?(ActiveRecord::SubclassNotFound)
sti_clean_up(exception.cause)
else
ex_message = exception.message
Foreman::Logging.exception(ex_message, exception)
full_request_id = request.request_id
render :template => "common/500", :layout => !request.xhr?, :status => :internal_server_error, :locals => { exception_message: ex_message, request_id: full_request_id.split('-').first, full_request_id: full_request_id }
end
end
def check_empty_taxonomy
return if ["locations", "organizations"].include?(controller_name)
if User.current&.admin?
if Location.unconfigured?
redirect_to main_app.locations_path, :info => _("You must create at least one location before continuing.")
elsif Organization.unconfigured?
redirect_to main_app.organizations_path, :info => _("You must create at least one organization before continuing.")
end
end
end
# Returns the associations to include when doing a search.
# If the user has a fact_filter then we need to include :fact_values
# We do not include most associations unless we are processing a html page
def included_associations(include = [])
include + [:hostgroup, :compute_resource, :operatingsystem, :model, :host_statuses, :token]
end
def errors_hash(errors)
errors.any? ? {:status => N_("Error"), :message => errors.full_messages.join('<br>')} : {:status => N_("OK"), :message => ""}
end
def taxonomy_scope
if params[controller_name.singularize.to_sym]
@organization = Organization.find_by_id(params[controller_name.singularize.to_sym][:organization_id])
@location = Location.find_by_id(params[controller_name.singularize.to_sym][:location_id])
end
if instance_variable_get("@#{controller_name}").present?
@organization ||= instance_variable_get("@#{controller_name}").organization
@location ||= instance_variable_get("@#{controller_name}").location
end
@organization ||= Organization.find_by_id(params[:organization_id]) if params[:organization_id]
@location ||= Location.find_by_id(params[:location_id]) if params[:location_id]
@organization ||= Organization.current
@location ||= Location.current
end
def parameter_filter_context
Foreman::ParameterFilter::Context.new(:ui, controller_name, params[:action])
end
class << self
def parameter_filter_context
Foreman::ParameterFilter::Context.new(:ui, controller_name, nil)
end
end
end