|
class ApplicationController < ActionController::Base
|
|
include ApplicationShared
|
|
|
|
include Foreman::Controller::Flash
|
|
|
|
force_ssl :if => :require_ssl?
|
|
protect_from_forgery # 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
|
|
|
|
# standard layout to all controllers
|
|
helper 'layout'
|
|
helper_method :authorizer, :resource_path
|
|
|
|
before_action :require_login
|
|
before_action :set_gettext_locale_db, :set_gettext_locale
|
|
before_action :session_expiry, :update_activity_time, :unless => proc {|c| !SETTINGS[:login] || c.remote_user_provided? || c.api_request? }
|
|
before_action :set_taxonomy, :require_mail, :check_empty_taxonomy
|
|
before_action :authorize
|
|
before_action :welcome, :only => :index, :unless => :api_request?
|
|
prepend_before_action :allow_webpack, if: -> { Rails.configuration.webpack.dev_server.enabled }
|
|
around_action :set_timezone
|
|
layout :display_layout?
|
|
|
|
attr_reader :original_search_parameter
|
|
|
|
def welcome
|
|
if (model_of_controller.first.nil? rescue false)
|
|
@welcome = true
|
|
render :welcome rescue nil
|
|
end
|
|
rescue
|
|
not_found
|
|
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 authorizer
|
|
@authorizer ||= Authorizer.new(User.current, :collection => instance_variable_get("@#{controller_name}"))
|
|
end
|
|
|
|
def deny_access
|
|
(User.current.logged? || request.xhr?) ? render_403 : require_login
|
|
end
|
|
|
|
def require_ssl?
|
|
SETTINGS[:require_ssl]
|
|
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
|
|
if User.current && !User.current.hidden? && User.current.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.current)
|
|
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 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('/', '::').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.where(nil)
|
|
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.keys.each do |param|
|
|
if param =~ /(\w+)_id$/
|
|
if params[param].present?
|
|
query = "#{Regexp.last_match(1)} = #{params[param]}"
|
|
params[:search] += query unless params[:search].include? 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["REMOTE_USER"]).present?
|
|
end
|
|
|
|
def display_layout?
|
|
return false if two_pane?
|
|
"application"
|
|
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)
|
|
end
|
|
|
|
def saved_redirect_url_or(default)
|
|
session["redirect_to_url_#{controller_name}"] || default
|
|
end
|
|
|
|
def generic_exception(exception)
|
|
Foreman::Logging.exception("Action failed", exception)
|
|
render :template => "common/500", :layout => !request.xhr?, :status => :internal_server_error, :locals => { :exception => exception}
|
|
end
|
|
|
|
def check_empty_taxonomy
|
|
return if ["locations", "organizations"].include?(controller_name)
|
|
|
|
if User.current&.admin?
|
|
if SETTINGS[:locations_enabled] && Location.unconfigured?
|
|
redirect_to main_app.locations_path, :info => _("You must create at least one location before continuing.")
|
|
elsif SETTINGS[:organizations_enabled] && 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, :environment, :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 if SETTINGS[:organizations_enabled]
|
|
@location ||= Location.current if SETTINGS[:locations_enabled]
|
|
end
|
|
|
|
def two_pane?
|
|
request.headers["X-Foreman-Layout"] == 'two-pane' && params[:action] != 'index'
|
|
end
|
|
|
|
# Called from ActionController::RequestForgeryProtection, overrides
|
|
# nullify session which is the default behavior for unverified requests in Rails 3.
|
|
# On Rails 4 we can get rid of this and use the strategy ':exception'.
|
|
def handle_unverified_request
|
|
raise ::Foreman::Exception.new(N_("Invalid authenticity token"))
|
|
end
|
|
|
|
def parameter_filter_context
|
|
Foreman::ParameterFilter::Context.new(:ui, controller_name, params[:action])
|
|
end
|
|
|
|
def allow_webpack
|
|
webpack_csp = { script_src: [webpack_server], connect_src: [webpack_server],
|
|
style_src: [webpack_server], img_src: [webpack_server] }
|
|
|
|
append_content_security_policy_directives(webpack_csp)
|
|
end
|
|
|
|
def webpack_server
|
|
port = Rails.configuration.webpack.dev_server.port
|
|
@dev_server ||= "#{request.protocol}#{request.host}:#{port}"
|
|
end
|
|
|
|
class << self
|
|
def parameter_filter_context
|
|
Foreman::ParameterFilter::Context.new(:ui, controller_name, nil)
|
|
end
|
|
end
|
|
end
|