concord-consortium/rigse

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

Summary

Maintainability
C
7 hrs
Test Coverage
require 'haml'
require 'will_paginate/array'

BrowserSpecificiation = Struct.new(:browser, :version)

class PunditUserContext
  attr_reader :user, :original_user, :request, :params

  def initialize(user, original_user, request, params)
    @user = user
    @original_user = original_user
    @request = request
    @params = params
  end
end

class ApplicationController < ActionController::Base
  include Clipboard
  include Pundit

  protect_from_forgery prepend: false

  rescue_from Pundit::NotAuthorizedError, with: :pundit_user_not_authorized

  def pundit_user
    PunditUserContext.new(current_user, @original_user, request, params)
  end

  # With +respond_to do |format|+, "406 Not Acceptable" is sent on invalid format.
  # With a regular render (implicit or explicit), ActionView::MissingTemplate
  # exception is raised instead. The MissingTemplate exception triggers an
  # exception notification that we don't really care about.
  # So instead we catch that and raise a RoutingError which Rails turns
  # into a 404 response.
  rescue_from(ActionView::MissingTemplate) do |e|
    request.format = :html
    raise ActionController::RoutingError.new('Not Found')
  end

  layout 'application'
  def test
    render :html => mce_in_place_tag(Page.create,'description','none')
  end


  # helper :all # include all helpers, all the time
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

  before_action :setup_container
  before_action :reject_old_browsers

  include AuthenticatedSystem
  include RoleRequirementSystem

  helper :all # include all helpers, all the time

  before_action :original_user
  before_action :portal_resources
  before_action :check_for_select_portal_user_type
  before_action :check_for_password_reset_requirement
  before_action :check_student_security_questions_ok
  before_action :check_student_consent
  before_action :set_locale
  before_action :wide_layout_for_anonymous

  # Portal::School.find(:first).members.count

  protected

  def pundit_user_not_authorized(exception)
    # without the no-store Chrome will cache this redirect in some cases
    # for example if a student tries to access a collection page, and then they
    # log out and try to access it again. In this case Chrome sends them to the
    # cached location of "/my-classes". By default rails adds 'no-cache' but that isn't
    # strong enough.
    response.headers['Cache-Control'] = 'no-store'

    error_message = not_authorized_error_message
    if request.xhr?
      render :html => "<div class='flash_error'>#{error_message}</div>", :status => 403
    else
      if current_user
        if BoolENV['RESEARCHER_REPORT_ONLY']
          # if we are here then current user is not authorized to access the reports.
          # The normal code path would send them in a redirect loop
          # instead sign them out and show them a page telling them this ia report only portal
          sign_out :user
          redirect_to learner_report_only_path
        else
          # only show the error alert if we are not redirecting after signing in
          # An error on redirecting after signing in should only happen in two cases:
          # 1. the user was logged out and then clicked on an restricted link, then the user
          #    didn't actually log in, but just left the page there. Then a different user
          #    logged in. This new user didn't have access to the page of the original user
          #    so this exception was thrown during the automatic redirect to the original
          # 2. A anonymous user tried to access something they shouldn't access. They should
          #    have been shown a message and directed to the login page.  Now if the user
          #    logs in the portal will redirect to this initial page. Since the user already
          #    saw the message there is no need to show it again.
          # So instead of showing the error message again, we just send the user to the
          # default login page for that user.
          flash['alert'] = error_message if not params[:redirecting_after_sign_in]

          redirect_to view_context.current_user_home_path
        end
      else
        flash['alert'] = error_message
        # send the anonymous user to the login page, and then try to send the user back
        # to the original page. In the case of a post request this won't always work so
        # well. It will redirect the user to the GET route of the same URL that was posted
        # to. Often this is the index page of the resource.
        redirect_to auth_login_path(after_sign_in_path: request.path)
      end
    end
  end

  def setup_container
    @container_type = self.class.name[/(.+)sController/,1]
    @container_id =  request.path_parameters.symbolize_keys[:id]
  end

  def current_settings
    @_settings ||= Admin::Settings.default_settings
  end

  def configured_search_path
    if current_settings && current_settings.custom_search_path.present?
      current_settings.custom_search_path
    else
      search_path # config/routes.rb
    end
  end
  # Make this method available as a helper method too (in templates).
  helper_method :configured_search_path

  # Automatically respond with 404 for ActiveRecord::RecordNotFound
  def record_not_found
    render :file => File.join(::Rails.root.to_s, 'public', '404.html'), :formats => [:html], :status => 404
  end


  def param_find(token_sym, force_nil=false)
    token = token_sym.to_s
    result = nil
    eval_string = <<-EOF
      if params[:#{token}]
        result = session[:#{token}] = cookies[:#{token}] = params[:#{token}]
      elsif force_nil
         session[:#{token}] = cookies[:#{token}] = nil
      else
        result = session[:#{token}] || cookies[:#{token}]
      end
    EOF
    eval eval_string
    result = nil if result == ""
    result
  end


  def get_scope(default)
    begin
      @scope = default
      if container_type = params[:scope_type]
        @scope = container_type.constantize.find(params[:scope_id])
      elsif (container_type = params[:container_type]) && params[:container_id]
        @scope = container_type.constantize.find(params[:container_id])
      end
      @scope
    rescue ActiveRecord::RecordNotFound
      nil
    end
  end

  def valid_uuid(value)
    value.is_a?(String) && value.length == 36
  end

  def humanized_action(map={})
    key = action_name.to_sym
    if map.key?(key)
      name = map[key]
    else
      name = case action_name
      when "index" then "list"
      when "new", "duplicate" then "create"
      when "show" then "view"
      else action_name
      end
    end
    name.humanize
  end

  def not_authorized_error_message(options={})
    resource_type = options[:resource_type] || ''
    resource_name = options[:resource_name] || ''
    additional_info = options[:additional_info] || ''

    is_singular = action_name != "index"

    action = humanized_action.downcase
    error_message = "#{current_user.nil? ? "Anonymous users" : "You (#{current_visitor.login})"} can not #{action} the requested"
    error_message = "#{error_message} #{resource_name.empty? ? '' : "'#{resource_name}' "}#{resource_type.empty? ? 'resource' : resource_type.pluralize(is_singular ? 1 : 2 )}"
    error_message = "#{error_message}, #{additional_info}" if !additional_info.empty?
    error_message = "#{error_message}.  Please sign in to #{action} #{is_singular ? 'it' : 'them'}." if current_user.nil?

    error_message
  end

  private

  # setup the portal_teacher and student instance variables
  def portal_resources
    @portal_teacher = current_visitor.portal_teacher
    @portal_student = current_visitor.portal_student
  end

  # Accesses the user that this session originally logged in as.
  def original_user
    if session[:original_user_id]
      @original_user ||=  User.find(session[:original_user_id])
    else
      @original_user = current_visitor
    end
  end

  def redirect_back_or(path)
    redirect_back(fallback_location: path)
  end

  def session_sensitive_path
    path = request.env['PATH_INFO']
    return path =~ /password|session|sign_in|sign_out|security_questions|consent|help|user_type_selector/i
  end

  def check_for_select_portal_user_type
    if request.format && request.format.html? && current_visitor && current_visitor.require_portal_user_type && !current_visitor.has_portal_user_type?
      unless session_sensitive_path
        flash.keep

        #
        # TODO If there were some way render the underlying content of
        # the omniauth_origin url beneath the modal signup popup, that would
        # be nice.
        #
        # Also if this could be passed in, rather than stored in the
        # session, that would.
        #
        # Otherwise, this is not actually used.
        #
        redirect_to portal_user_type_selector_path(omniauth_origin: session["omniauth.origin"])

      end
    end
  end

  def check_for_password_reset_requirement
    if request.format && request.format.html? && current_visitor && current_visitor.require_password_reset
      unless session_sensitive_path
        flash.keep
        redirect_to change_password_path :reset_code => "0"
      end
    end
  end

  def check_student_security_questions_ok
    if request.format && request.format.html? && current_settings && current_settings.use_student_security_questions && !current_visitor.portal_student.nil? && current_visitor.security_questions.size < 3
      unless session_sensitive_path
        flash.keep
        redirect_to(edit_user_security_questions_path(current_visitor))
      end
    end
  end

  def check_student_consent
    if request.format && request.format.html? && current_settings && current_settings.require_user_consent? && !current_visitor.portal_student.nil? && !current_visitor.asked_age?
      unless session_sensitive_path
        flash.keep
        redirect_to(ask_consent_portal_student_path(current_visitor.portal_student))
      end
    end
  end

  # this is normally called by devise during the sessions#create action
  # so it has access to the parameters that were passed in. This allows us to pass
  # a hidden param :after_sign_in_path to the sign in form.
  def after_sign_in_path_for(resource)
    redirect_path = view_context.current_user_home_path

    if params[:after_sign_in_path].present?
      # the check for to see if the user has permission to view the after_sigin_in_path
      # page is handled by the controller of this new page.
      # if the user doesn't have permission to see the new page they will be sent to their
      # home page. They will also not see a error message because of the
      # redirecting_after_sign_in parameter that is added here.
      # See pundit_user_not_authorized for the implementation

      redirect_uri = URI.parse(params[:after_sign_in_path])

      # Only allow redirecting to paths. If the redirect url has a host do not redirect
      # this prevents an open redirect. More info about open redirects are here:
      # https://cwe.mitre.org/data/definitions/601.html
      if redirect_uri.host.nil?
        query = Rack::Utils.parse_query(redirect_uri.query)
        # add an extra param to this path, so we don't go in a loop, see pundit_user_not_authorized
        query["redirecting_after_sign_in"] = '1'
        redirect_uri.query = Rack::Utils.build_query(query)
        redirect_path = redirect_uri.to_s
      end
    end

    redirect_path
  end

  def after_sign_out_path_for(resource)
    root_path
  end

  def set_locale
    # Set locale according to theme
    name = ENV['THEME'].blank? ? "en" : "en-#{ENV['THEME'].upcase}"
    if I18n.available_locales.include?(name.to_sym)
      I18n.locale = name.to_sym
    end
  end

  def wide_layout_for_anonymous
    @wide_content_layout = true if current_visitor.anonymous?
  end

  def reject_old_browsers
    user_agent = UserAgent.parse(request.user_agent)
    min_browser = BrowserSpecificiation.new("Internet Explorer", "9.0")
    if user_agent < min_browser
      @wide_content_layout = true
      @user_agent = user_agent
      render 'home/bad_browser', :layout => "old_browser"
    end
  end

  private

  def get_theme
    ENV['THEME'].blank? ? 'learn' : ENV['THEME']
  end

end