testmycode/tmc-server

View on GitHub
app/controllers/application_controller.rb

Summary

Maintainability
C
7 hrs
Test Coverage
# frozen_string_literal: true

require 'version'
require 'twitter_bootstrap_breadcrumbs'

# Base class for all controllers.
#
# Includes common parts like handling some exceptions as well as
# various utility methods.
class ApplicationController < ActionController::Base
  include BreadCrumbs

  helper :all

  add_breadcrumb 'TMC', :root_path

  layout :select_layout

  protect_from_forgery
  include BootstrapFlashHelper
  include FlashBlockHelper
  include SessionsHelper
  include BreadcrumbHelpers
  include EmbeddableHelper
  include AuthorizeCollectionHelper
  include ApplicationHelper
  check_authorization

  rescue_from CanCan::AccessDenied do |_exception|
    if current_user.guest?
      redirect_to(login_path(return_to: return_to_link))
    else
      respond_forbidden
    end
  end

  rescue_from ActiveRecord::RecordNotFound do |exception|
    respond_with_error('Not found', 404, exception)
  end

  rescue_from ActionController::MissingFile do |exception|
    respond_with_error('File not found', 404, exception)
  end

  before_action :set_default_url_options
  before_action :check_api_version
  before_action :set_bare_layout
  before_action :set_controller_and_action_name
  before_action :setup_rack_mini_profiler

  def url_options
    if @bare_layout
      { bare_layout: '1' }.merge(super)
    else
      super
    end
  end

  private
    def current_ability
      @current_ability ||= ::Ability.new(current_user)
    end

    def set_default_url_options
      Rails.application.routes.default_url_options[:host] = request.host_with_port
    end

    def check_api_version
      if should_check_api_version?
        if params[:api_version].blank?
          return respond_with_error('Please update the TMC client. No API version received from client.', 404, nil, obsolete_client: true)
        elsif params[:api_version].to_s != ApiVersion::API_VERSION.to_s
          return respond_with_error("Please update the TMC client. API version #{ApiVersion::API_VERSION} required but got #{params[:api_version]}", 404, nil, obsolete_client: true)
        end

        if params[:client].present? # Client and client version checks are optional
          begin
            check_client_version(params[:client], params[:client_version])
          rescue StandardError
            respond_with_error($!.message, 404, nil, obsolete_client: true)
          end
        end
      end
    end

    def check_client_version(client_name, client_version)
      begin
        client_version = Version.new(client_version) unless client_version.nil?
      rescue StandardError
        raise "Invalid version string: #{client_version}"
      end

      valid_clients = SiteSetting.value('valid_clients')
      if valid_clients.is_a?(Enumerable)
        vc = valid_clients.find { |c| c['name'] == client_name }
        raise 'Invalid TMC client.' if vc.nil?

        if !client_version.nil? && vc['min_version'].present?
          raise 'Please update the TMC client.' if client_version < Version.new(vc['min_version'])
        else
          nil # without version check
        end
      end
    end

    def should_check_api_version?
      params[:format] == 'json' &&
        controller_name != 'stats' &&
        controller_name != 'auths' &&
        !(controller_name == 'feedback_answers' && action_name == 'index') &&
        !(controller_path.starts_with? 'api')
    end

    def set_bare_layout
      @bare_layout = !!params[:bare_layout]
    end

    def set_controller_and_action_name
      @controller_name = controller_name
      @action_name = action_name
    end

    def setup_rack_mini_profiler
      if current_user && current_user.administrator?
        Rack::MiniProfiler.authorize_request
      end
    end

    def unauthorized!(message = nil)
      raise CanCan::AccessDenied, message
    end

    def unauthorize_guest!(message = 'Authentication required')
      unauthorized!(message) if current_user.guest?
    end

    def only_admins!
      unauthorized! unless current_user.administrator?
    end

    def authorization_skip!
      authorize! :read, nil
    end

    def respond_not_found(msg = 'Not Found')
      respond_with_error(msg, 404)
    end

    def respond_forbidden(msg = 'Forbidden')
      respond_with_error(msg, 403)
    end

    def respond_unauthorized(msg = 'Authentication required')
      respond_with_error(msg, 401)
    end

    def respond_with_error(msg, code = 500, exception = nil, extra_json_keys = {})
      respond_to do |format|
        format.html do
          render 'shared/respond_with_error',
                 locals: { message: ERB::Util.html_escape(msg), exception: exception },
                 layout: true,
                 status: code
        end
        format.json do
          if code == 401 || code == 403
            # To support older TmcNetBeans versions using faulty http basic auth
            if client_supports_http_basic_auth?
              response.headers['WWW-Authenticate'] = "Basic realm=\"#{msg}\""
              render json: { error: msg }.merge(extra_json_keys), status: code
            else
              render json: { error: msg }.merge(extra_json_keys), status: :forbidden if code == 403
              render json: { error: msg }.merge(extra_json_keys), status: :unauthorized if code == 401
            end
          else
            render json: { error: msg }.merge(extra_json_keys), status: code
          end
        end
        format.text { render plain: 'ERROR: ' + msg, status: code }
        format.zip { render plain: msg, status: code, content_type: 'text/plain' }
      end
    end

    # To support older versions of tmc-netbeans-plugin
    def client_supports_http_basic_auth?
      return true if params[:client].blank?
      client = params[:client]
      client_version = begin
        Version.new(params['client_version']) if params['client_version'].present?
      rescue StandardError
      end
      !(client == 'netbeans_plugin' && client_version < Version.new('0.8.0'))
    end

    def select_layout
      if params[:bare_layout]
        'bare'
      else
        'application'
      end
    end

    def params_starting_with(prefix, permitted, options = {})
      options = {
        remove_prefix: false
      }.merge(options)

      permitted = permitted.map { |f| prefix + f } unless permitted == :all

      result = Hash[params.select do |k, v|
        k.start_with?(prefix) && v.present? && (permitted == :all || permitted.include?(k))
      end.permit(permitted)]
      if options[:remove_prefix]
        result = result.transform_keys { |k| k.sub(/^#{prefix}/, '') }
      end
      result
    end

    # To be called from a respond_to on csv.
    # http://stackoverflow.com/questions/94502/in-rails-how-to-return-records-as-a-csv-file
    def render_csv(options = {})
      options = {
        filename: action_name
      }.merge(options)

      headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
      headers['Expires'] = 'Thu, 01 Dec 1994 16:00:00 GMT'

      if /msie/i.match?(request.env['HTTP_USER_AGENT'])
        headers['Pragma'] = 'public'
        headers['Content-type'] = 'text/plain'
      else
        headers['Content-Type'] ||= 'text/csv'
      end
      headers['Content-Disposition'] = "attachment; filename=\"#{options[:filename]}\""

      render_options = {
        layout: false
      }.merge(options)
      render_options.delete(:filename)

      render render_options
    end

    def wrap_transaction(&block)
      ActiveRecord::Base.transaction(requires_new: true, &block)
    end
end