aeolusproject/conductor

View on GitHub
src/app/controllers/application_controller.rb

Summary

Maintainability
D
1 day
Test Coverage
#
#   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
  class ActionError < RuntimeError; end
  class PartialSuccessError < RuntimeError
    attr_reader :failures, :successes
    def initialize(msg, failures={}, successes=[])
      @failures = failures
      @successes = successes
      super(msg)
    end
  end

  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 render_api_error(errors, status = :unprocessable_entity)
    render :template => 'api/validation_error',
           :status   => status,
           :locals   => { :errors => errors }
  end

  def get_nav_items
    if current_user.present?
      @providers = Provider.list_for_user(current_session, current_user,
                                          Alberich::Privilege::VIEW)
      @pools = Pool.list_for_user(current_session, current_user,
                                  Alberich::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]
    json
  end

  def xml_errors(msg)
    xml = {}
    xml[:message] = msg
    xml.merge!(instance_errors)
    xml
  end

  def instance_errors
    hash = {}
    arr = []
    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
    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)
    user(:api)
  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 = _(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 => alberich.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 if model.column_names.include?(default)
    "#{model.quoted_table_name}.name"
  end

  def sort_direction
    %w[asc desc].include?(params[:order_dir]) ? params[:order_dir] : "asc"
  end

  def add_permissions_tab(perm_obj, path_prefix = '',
                          polymorphic_path_extras = {})
    add_permissions_common(false, perm_obj, path_prefix,
                           polymorphic_path_extras)
    if "permissions" == params[:details_tab]
      require_privilege(Alberich::Privilege::PERM_VIEW, perm_obj)
    end
    if perm_obj.has_privilege(current_session, current_user,
                              Alberich::Privilege::PERM_VIEW)
      if @tabs
        @tabs << {:name => _('Role Assignments'),
                  :view => 'alberich/permissions/permissions',
                  :id => 'permissions',
                  :count => perm_obj.permissions.count,
                  :pretty_view_toggle => 'disabled'}
      end
    end
  end

  def filter_permissions_for_profile(perms)
    paginate_collection(perms.
      apply_filters(:preset_filters_options =>
                      Alberich::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])
  end

  def filter_permissions(perms)
    paginate_collection(perms.
      apply_filters(:preset_filter_id => params[:permissions_preset_filter],
                    :search_filter => params[:permissions_search]),
                                       params[:page])
  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