src/app/controllers/application_controller.rb
#
# Copyright 2011 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
require 'viewstate.rb'
require 'util/conductor'
require 'will_paginate/array'
class ApplicationController < ActionController::Base
# FIXME: not sure what we're doing aobut service layer w/ deltacloud
include ApplicationService
helper_method :current_session, :current_user, :filter_view?
before_filter :read_breadcrumbs, :set_locale, :check_session_expiration
# General error handlers, must be in order from least specific
# to most specific
rescue_from Exception, :with => :handle_general_error
rescue_from PermissionError, :with => :handle_perm_error
rescue_from ActionError, :with => :handle_action_error
rescue_from PartialSuccessError, :with => :handle_partial_success_error
rescue_from ActiveRecord::RecordNotFound, :with => :handle_active_record_not_found_error
rescue_from Aeolus::Conductor::API::Error, :with => :handle_api_error
helper_method :check_privilege
helper_method :paginate_collection
protected
# permissions checking
def handle_perm_error(error)
if params[:return_from_permission_change]
flash.now
redirect_to main_app.account_url
return
end
handle_error(:error => error, :status => :forbidden,
:title => _('Access denied'))
end
def handle_partial_success_error(error)
failures_arr = error.failures.collect do |resource, reason|
if resource.respond_to?(:display_name)
resource.display_name + ": " + reason
else
reason
end
end
@successes = error.successes
@failures = error.failures
handle_error(:error => error, :status => :ok,
:message => error.message + ": " + failures_arr.join(", "),
:title => _('Some actions failed'))
end
def handle_action_error(error)
handle_error(:error => error, :status => :conflict,
:title => _('Action Error'))
end
def handle_general_error(error)
handle_error(:error => error, :status => :internal_server_error,
:title => _('Internal Server Error'),
:status => 500)
end
def handle_error(hash)
log_backtrace(hash[:error]) if hash[:error]
msg = hash[:message] || hash[:error].message
title = hash[:title] || _('Internal Server Error')
status = hash[:status] || _('Internal Server Error')
respond_to do |format|
format.html { html_error_page(title, msg, status) }
format.json { render :json => json_error_hash(msg, status) }
format.xml { render :xml => xml_errors(msg), :status => status }
end
end
def html_error_page(title, msg, status)
if request.xhr?
render :partial => 'layouts/popup-error', :status => status,
:locals => {:title => title, :errmsg => msg}
else
flash.now[:error] = msg
render :template => 'layouts/error', :layout => 'application',
:locals => {:title => title, :errmsg => ''},
:status => status
end
end
def get_nav_items
if current_user.present?
@providers = Provider.list_for_user(current_session, current_user,
Privilege::VIEW)
@pools = Pool.list_for_user(current_session, current_user,
Privilege::VIEW)
end
end
def handle_active_record_not_found_error(error)
respond_to do |format|
format.html do
redirect_to :controller => params[:controller]
flash[:notice] = _('The record you tried to access does not exist. It may have been deleted')
end
format.xml { render :template => 'api/error', :locals => {:error => error}, :status => :not_found }
end
end
def handle_api_error(error)
render :template => 'api/error', :locals => {:error => error}, :status => error.status
end
# Returns an array of ids from params[:id], params[:ids].
def ids_list(other_attrs=[])
other_attrs = Array(other_attrs) unless other_attrs.is_a?(Array)
other_attrs.each do |attr_key|
return Array(params[attr_key]) if params.include?(attr_key)
end
if params[:id].present?
return Array(params[:id])
elsif params[:ids].present?
return Array(params[:ids])
end
end
# let's suppose that 'pretty' view is default
def filter_view?
if params[:view].present?
params[:view] == 'filter'
elsif params[:details_tab].present?
true
elsif @viewstate
@viewstate.state['view'] == 'filter'
end
end
private
def json_error_hash(msg, status)
json = {}
json[:success] = (status == :ok)
json.merge!(instance_errors)
# There's a potential issue here: if we add :errors for an object
# that the view won't generate inline error messages for, the user
# won't get any indication what the error is. But if we set :alert
# unconditionally, the user will get validation errors twice: once
# inline in the form, and once in the flash
json[:alert] = msg unless json[:errors]
return json
end
def xml_errors(msg)
xml = {}
xml[:message] = msg
xml.merge!(instance_errors)
return xml
end
def instance_errors
hash = {}
arr = Array.new
instance_variables.each do |ivar|
val = instance_variable_get(ivar)
if val && val.respond_to?(:errors) && val.errors.respond_to?(:size) && val.errors.size > 0
hash[:object] = ivar[1, ivar.size]
hash[:errors] ||= []
val.errors.each {|key,msg|
arr.push([key, Array(msg)].to_a)
}
hash[:errors] += arr
end
end
return hash
end
def http_auth_user
return unless request.authorization && request.authorization =~ /^Basic (.*)/m
authenticate!(:scope => :api)
frozen = request.session_options.frozen?
request.session_options = request.session_options.dup if frozen
request.session_options[:expire_after] = 2.minutes
request.session_options.freeze if frozen
# we use :api scope for authentication to avoid saving session.
# But it's handy to set authenticated user in default scope, so we
# can use current_user, instead of current_user(:api)
env['warden'].set_user(user(:api)) if user(:api)
return user(:api)
end
def current_session
@current_session ||= PermissionSession.find_by_id(session[:permission_session_id])
end
def require_user
return if current_user or http_auth_user
respond_to do |format|
format.html do
store_location
unless session[:return_to] == root_path # don't display notice if going to root
flash[:warning] = _('Please log in first.')
end
redirect_to main_app.login_url
end
format.js { head :unauthorized }
format.xml { head :unauthorized }
format.json { head :unauthorized }
end
end
def require_user_api
return if current_user or http_auth_user
respond_to do |format|
format.xml { head :unauthorized }
end
end
def require_no_user
return true unless current_user
store_location
flash[:notice] = _('You must be logged out to access this page')
redirect_to main_app.account_url
end
def store_location
session[:return_to] = request.get? ? request.fullpath : request.referer
end
def back_or_default_url(default)
return session[:return_to] || default
session[:return_to] = nil
end
############################################################################
# Breadcrumb-Related functionality
############################################################################
def read_breadcrumbs
session[:breadcrumbs] ||= []
@read_breadcrumbs = session[:breadcrumbs].last(2)
end
def clear_breadcrumbs
return if request.format == :json
session[:breadcrumbs] = []
end
def save_breadcrumb(path, name = controller_name)
return if request.format == :json
session[:breadcrumbs] ||= []
breadcrumbs = session[:breadcrumbs]
viewstate = @viewstate ? @viewstate.id : nil
#if item with desired path is already in bc, then remove every bc behind it
if index = breadcrumbs.find_index {|b| b[:path] == path || path.split('?')[0] == b[:path] }
breadcrumbs.slice!(index, breadcrumbs.length)
end
read_breadcrumbs
name = t("breadcrumbs.#{name}") if self.controller_name.eql?(name)
breadcrumbs.push({:name => name, :path => path, :viewstate => viewstate, :class => self.controller_name})
session[:breadcrumbs] = breadcrumbs
end
def set_admin_content_tabs(tab)
@tabs = [{:name => _('Catalogs'), :url => main_app.catalogs_url, :id => 'catalogs'},
{:name => _('Realms'), :url => main_app.frontend_realms_url, :id => 'frontend_realms'},
{:name => _('Hardware'), :url => main_app.hardware_profiles_url, :id => 'hardware_profiles'},
]
unless @details_tab = @tabs.find {|t| t[:id] == tab}
raise "Tab '#{tab}' doesn't exist"
end
end
def set_admin_users_tabs(tab)
@tabs = [{:name => _('Users'), :url => main_app.users_url, :id => 'users'},
{:name => _('User Groups'), :url => main_app.user_groups_url, :id => 'user_groups'},
{:name => _('Global Role Grants'), :url => main_app.permissions_url, :id => 'permissions'},
]
unless @details_tab = @tabs.find {|t| t[:id] == tab}
raise "Tab '#{tab}' doesn't exist"
end
end
def set_admin_environments_tabs(tab)
@tabs = [{:name => _('Environments'), :url => main_app.pool_families_url, :id => 'pool_families'},
{:name => _('Images'), :url => tim.base_images_url, :id => 'images'},
]
unless @details_tab = @tabs.find {|t| t[:id] == tab}
raise "Tab '#{tab}' doesn't exist"
end
end
def sort_column(model, default = nil)
return params[:order_field] if model.column_names.include?(params[:order_field])
return default unless default.nil? || !model.column_names.include?(default)
return "#{model.quoted_table_name}.name"
end
def sort_direction
%w[asc desc].include?(params[:order_dir]) ? params[:order_dir] : "asc"
end
def add_profile_permissions_inline(entity, path_prefix = '')
@entity = entity
@path_prefix = path_prefix
@roles = Role.all_by_scope
@inline = true
@show_inherited = params[:show_inherited]
@show_global = params[:show_global]
if @show_inherited
local_perms = @entity.derived_permissions
else
local_perms = @entity.permissions
end
@permissions = paginate_collection(local_perms.
apply_filters(:preset_filters_options =>
Permission::PROFILE_PRESET_FILTERS_OPTIONS,
:preset_filter_id =>
params[:profile_permissions_preset_filter],
:search_filter =>
[params[:profile_permissions_search],
params[:profile_permissions_preset_filter]]),
params[:page])
@permission_list_header = []
unless (@show_inherited)
@permission_list_header <<
{ :name => 'checkbox', :class => 'checkbox', :sortable => false }
end
@permission_list_header += [
{ :name => _('Resource Type')},
{ :name => _('Resource')},
{ :name => _('Role'), :sort_attr => :role},
]
if @show_inherited
@permission_list_header <<
{ :name => _('Inherited From'), :sortable => false }
end
end
def add_permissions_inline(perm_obj, path_prefix = '', polymorphic_path_extras = {})
@permission_object = perm_obj
require_privilege(Privilege::VIEW, @permission_object)
@path_prefix = path_prefix
@polymorphic_path_extras = polymorphic_path_extras
@roles = Role.find_all_by_scope(@permission_object.class.name)
set_permissions_header
@inline = true
end
def add_permissions_tab(perm_obj, path_prefix = '', polymorphic_path_extras = {})
@inline = false
@path_prefix = path_prefix
@polymorphic_path_extras = polymorphic_path_extras
@permission_object = perm_obj
if "permissions" == params[:details_tab]
require_privilege(Privilege::PERM_VIEW, perm_obj)
end
if perm_obj.has_privilege(current_session, current_user, Privilege::PERM_VIEW)
@roles = Role.find_all_by_scope(@permission_object.class.name)
if @tabs
@tabs << {:name => _('Role Assignments'),
:view => 'permissions/permissions',
:id => 'permissions',
:count => perm_obj.permissions.count,
:pretty_view_toggle => 'disabled'}
end
set_permissions_header
end
end
def set_permissions_header
unless @permissions_object == BasePermissionObject.general_permission_scope
@show_inherited = params[:show_inherited]
@show_global = params[:show_global]
end
if @show_inherited
local_perms = @permission_object.derived_permissions
elsif @show_global
local_perms = BasePermissionObject.general_permission_scope.
permissions_for_type(@permission_object.class)
else
local_perms = @permission_object.permissions
end
@permissions = paginate_collection(local_perms.
apply_filters(:preset_filter_id => params[:permissions_preset_filter],
:search_filter => params[:permissions_search]),
params[:page])
@permission_list_header = []
unless (@show_inherited or @show_global)
@permission_list_header <<
{ :name => 'checkbox', :class => 'checkbox', :sortable => false }
end
@permission_list_header += [
{ :name => _('Name')},
{ :name => _('Role'), :sort_attr => :role},
]
if @show_inherited
@permission_list_header <<
{ :name => _('Inherited From'), :sortable => false }
end
end
def set_locale
I18n.locale = env.nil? || env['HTTP_ACCEPT_LANGUAGE'].nil? ? I18n.default_locale : detect_locale
end
def detect_locale
# Processes the HTTP_ACCEPT_LANGUAGE header
languages = env['HTTP_ACCEPT_LANGUAGE'].split(',')
prefs = []
languages.each do |language|
language += ";q=1.0" unless language.match(";q=\d+\.\d+")
lang_code, lang_weight = language.split(";q=")
lang_code = lang_code.gsub(/-[a-z]+$/i) { |x| x.upcase }.to_sym
prefs << [lang_weight, lang_code]
end
# Orders the requested languages only by weight
ordered_languages = prefs.sort_by{ |weight, code| weight }.reverse.collect{|p| p[1]}.uniq
# Returns the lang_code if a requested language is a substring of an available language or vice-versa
ordered_languages.each do |ordered_lang_code|
I18n.available_locales.each do |available_lang_code|
return available_lang_code if available_lang_code.to_s[ordered_lang_code.to_s] || ordered_lang_code.to_s[available_lang_code.to_s]
end
end
# Returns the default_locale otherwise
I18n.default_locale
end
# In cases when current_path contains prefix like '/conductor' recognize_path fails.
# It needs to be stripped out in order to get the path correctly.
def recognize_path_with_relative_url_root(path)
root = ENV['RAILS_RELATIVE_URL_ROOT'] || ''
path = path.slice(root.length..path.length) if path.starts_with? root
Rails.application.routes.recognize_path(path)
end
def redirect_to_original(extended_params = {})
path = recognize_path_with_relative_url_root(params[:current_path])
original_params = Rack::Utils.parse_nested_query(URI.parse(params[:current_path]).query)
redirect_to path.merge(original_params).merge(extended_params)
end
def paginate_collection(collection, page = nil, per_page = PER_PAGE)
result = collection.paginate(:page => page, :per_page => per_page)
result = collection.paginate(:page => result.total_pages, :per_page => per_page) if result.out_of_bounds? && result.total_pages > 0
result
end
# before filter to invalidate session for backbone
def check_session_expiration
# if last_active_request not set previously, set it now
# so that it can be tracked from now on
session[:last_active_request] ||= Time.now
# compare to last non-backbone request time
logout if session[:last_active_request] < SETTINGS_CONFIG[:session][:timeout].minutes.ago
# FIXME: we really need a better "is it backbone?" test than json format
# since other actions (ajax API calls from UI, etc) could also use json
unless request.format == :json
# reset last active request time
session[:last_active_request] = Time.now
end
end
end