module Incline::Extensions::ActionControllerBase

Adds some extra functionality to the base controller definition.

Public Class Methods

included(base) click to toggle source

Enforces SSL and catches Incline::NotLoggedIn exceptions.

# File lib/incline/extensions/action_controller_base.rb, line 172
def self.included(base)
  base.extend ClassMethods
  base.class_eval do

    # Force SSL under production environments unless allow_http_for_request returns true.
    if Rails.env.production?
      force_ssl unless: :allow_http_for_request?
    end

    # Process user authorization for all actions except the GET/POST api actions.
    # These get to be authorized once the actual action is selected.
    before_action :valid_user?, except: [ :api ]

    undef process_action

    ##
    # Override the default to enable auto-api behavior if desired.
    def process_action(method_name, *args) #:nodoc:
      if self.class.auto_api?
        unless process_api_action(false, nil)
          super method_name, *args
        end
      else
        super method_name, *args
      end
    end

    rescue_from ::Incline::NotLoggedIn do |exception|
      flash[:info] = exception.message
      store_location
      redirect_to(incline.login_url)
    end

  end
end

Public Instance Methods

redirect_back_or(default) click to toggle source

A redirects to a previously stored location or to the default location.

Usually this will be used to return to an action after a user logs in.

# File lib/incline/extensions/action_controller_base.rb, line 230
def redirect_back_or(default)
  redirect_to session[:forwarding_url] || default
  session.delete :forwarding_url
end
render_csv(filename = nil, view_name = nil) click to toggle source

Renders the view as a CSV file.

Set the filename you would like to provide to the client, or leave it nil to use the action name. Set the view_name you would like to render, or leave it nil to use the action name.

# File lib/incline/extensions/action_controller_base.rb, line 213
def render_csv(filename = nil, view_name = nil)

  filename ||= params[:action]
  view_name ||= params[:action]
  filename.downcase!
  filename += '.csv' unless filename[-4..-1] == '.csv'

  headers['Content-Type'] = 'text/csv'
  headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""

  render view_name, layout: false
end
store_location() click to toggle source

Stores the current URL to be used with redirect_back_or.

# File lib/incline/extensions/action_controller_base.rb, line 237
def store_location
  session[:forwarding_url] = request.url if request.get?
end

Protected Instance Methods

allow_anon_for_request?() click to toggle source

Determines if the current request can be allowed with an anonymous user.

Overridden by require_admin_for_request? Implied by require_anon_for_request?

# File lib/incline/extensions/action_controller_base.rb, line 371
def allow_anon_for_request? #:doc:
  self.class.allow_anon_for? params[:action]
end
allow_http_for_request?() click to toggle source

Determines if the current request can be allowed via HTTP (non-SSL).

# File lib/incline/extensions/action_controller_base.rb, line 362
def allow_http_for_request? #:doc:
  self.class.allow_http_for? params[:action]
end
authorize!(*accepted_groups) click to toggle source

Authorizes access for the action.

  • With no arguments, this will validate that a user is currently logged in, but does not check their permission.

  • With an argument of true, this will validate that the user currently logged in is an administrator.

  • With one or more strings, this will validate that the user currently logged in has at least one or more of the named permissions.

    authorize!
    authorize! true
    authorize! "Safety Manager", "HR Manager"
    

If no user is logged in, then the user will be redirected to the login page and the method will return false. If a user is logged in, but is not authorized, then the user will be redirected to the home page and the method will return false. If the user is authorized, the method will return true.

# File lib/incline/extensions/action_controller_base.rb, line 282
def authorize!(*accepted_groups) #:doc:
  begin

    # an authenticated user must exist.
    unless logged_in?

      store_location

      if (auth_url = ::Incline::UserManager.begin_external_authentication(request))
        ::Incline::Log.debug 'Redirecting for external authentication.'
        redirect_to auth_url
        return false
      end

      raise_not_logged_in "You need to login to access '#{request.fullpath}'.",
                          'nobody is logged in'
    end

    if system_admin?
      log_authorize_success 'user is system admin'
    else
      # clean up the group list.
      accepted_groups ||= []
      accepted_groups.flatten!
      accepted_groups.delete false
      accepted_groups.delete ''

      if accepted_groups.include?(true)
        # group_list contains "true" so only a system admin may continue.
        raise_authorize_failure "Your are not authorized to access '#{request.fullpath}'.",
                                  'requires system administrator'

      elsif accepted_groups.blank?
        # group_list is empty or contained nothing but empty strings and boolean false.
        # everyone can continue.
        log_authorize_success 'only requires authenticated user'

      else
        # the group list contains one or more authorized groups.
        # we want them to all be uppercase strings.
        accepted_groups = accepted_groups.map{|v| v.to_s.upcase}.sort
        result = current_user.has_any_group?(*accepted_groups)
        unless result
          raise_authorize_failure "You are not authorized to access '#{request.fullpath}'.",
                                  "requires one of: #{accepted_groups.inspect}"
        end
        log_authorize_success "user has #{result.inspect}"
      end
    end

  rescue ::Incline::NotAuthorized => err
    flash[:danger] = err.message
    redirect_to main_app.root_url
    return false
  end
  true

end
inline_request?() click to toggle source

Is the current request an inline request?

JSON requests are always considered inline, otherwise we check to see if the “inline” parameter is set to a true value.

Primarily this would be used to strip the layour from rendered content.

# File lib/incline/extensions/action_controller_base.rb, line 356
def inline_request?
  json_request? || params[:inline].to_bool
end
json_request?() click to toggle source

Is the current request for JSON data?

Redirects are generally bad when JSON data is requested. For instance a `create` request in JSON format should return data, not redirect to `show`.

# File lib/incline/extensions/action_controller_base.rb, line 346
def json_request? #:doc:
  request.format.to_s =~ /^(application\/)?json$/i
end
map_api_action(requested_action) click to toggle source

This method maps the requested action into the actual action performed.

By default, the requested action is returned. If for some reason you want to use a different action in your API, override this method in your controller and map the actions as appropriate.

The actions you can expect to translate are 'index', 'new', 'show', and 'edit' for GET requests, and 'create', 'update', and 'destroy' for POST requests. They will be string values or nil, they will not be symbols.

The default behavior causes the API to “fall through” to your standard controller methods.

The API is accessed by either a POST or a GET request that specifies an `action` parameter. The action gets translated to the acceptable actions listed above and then run through this method for translation.

# File lib/incline/extensions/action_controller_base.rb, line 261
def map_api_action(requested_action) #:doc:
  requested_action
end
require_admin_for_request?() click to toggle source

Determines if the current request requires a system administrator.

Overrides all other access requirements.

# File lib/incline/extensions/action_controller_base.rb, line 379
def require_admin_for_request? #:doc:
  self.class.require_admin_for? params[:action]
end
require_anon_for_request?() click to toggle source

Determines if the current request requires an anonymous user.

Overridden by require_admin_for_request? Implies allow_anon_for_request?

# File lib/incline/extensions/action_controller_base.rb, line 388
def require_anon_for_request? #:doc:
  self.class.require_anon_for? params[:action]
end

Private Instance Methods

log_authorize_failure(message, log_message = nil) click to toggle source
# File lib/incline/extensions/action_controller_base.rb, line 527
def log_authorize_failure(message, log_message = nil)
  log_message ||= message
  Incline::Log::info "AUTH(FAILURE): #{request.fullpath}, #{current_user}, #{log_message}"
end
log_authorize_success(message) click to toggle source
# File lib/incline/extensions/action_controller_base.rb, line 532
def log_authorize_success(message)
  Incline::Log::info "AUTH(SUCCESS): #{request.fullpath}, #{current_user}, #{message}"
end
process_api_action(raise_on_invalid_action = true, default_get_action = 'index') click to toggle source
# File lib/incline/extensions/action_controller_base.rb, line 447
def process_api_action(raise_on_invalid_action = true, default_get_action = 'index')
  api_action =
      map_api_action(
          if request.post?
            # A post request can create, update, or destroy.
            # In addition we allow index to post since DataTables can send quite a bit of data with server-side
            # processing.
            # The post action can be provided as a query parameter or a form parameter with the form parameter
            # taking priority.
            {
                nil => 'index',
                'list' => 'index',
                'index' => 'index',
                'new' => 'create',
                'create' => 'create',
                'edit' => 'update',
                'update' => 'update',
                'remove' => 'destroy',
                'destroy' => 'destroy'
            }[request.request_parameters.delete('action') || request.query_parameters.delete('action')]
          elsif request.get?
            # For the most part, these shouldn't be used except for maybe index and show.
            # The new and edit actions could be used if for some reason you need to supply data to the ajax form
            # calling these actions, e.g. - a list of locations.
            # Datatables Editor does not use any of them.
            # Datatables will simple be requesting the index action in JSON format.
            # To remain consistent though, you can use '?action=list' to perform the same feat.
            {
                'list' => 'index',
                'index' => 'index',
                'new' => 'new',
                'show' => 'show',
                'edit' => 'edit'
            }[request.query_parameters.delete('action')] || default_get_action
          else
            nil
          end
      )

  if api_action
    # Datatables Editor sends the 'data' in the 'data' parameter.
    # We simple want to move that up a level so `params[:data][:user][:name]` becomes `params[:user][:name]`.
    data = request.params.delete('data')
    unless data.blank?
      # get the first entry in the hash.
      id, item = data.first

      # if the id is in AAAAA_NNN format, then we can extract the ID from it.
      if id.include?('_')
        id,_, item_id = id.rpartition('_')
        params[:id] = item_id.to_i
      end

      # merge the item data into the params array.
      params.merge!(id => item)
    end

    # since we are processing for an API request, we should return JSON.
    request.format = :json

    # finally, we'll start over on processing the stack.
    # This should ensure the appropriate `before_action` filters are called for the new action.
    process api_action
  else
    # raise an error.
    raise Incline::InvalidApiCall, 'Invalid API Action' if raise_on_invalid_action
    nil
  end
end
raise_authorize_failure(message, log_message = nil) click to toggle source
# File lib/incline/extensions/action_controller_base.rb, line 517
def raise_authorize_failure(message, log_message = nil)
  log_authorize_failure message, log_message
  raise Incline::NotAuthorized.new(message)
end
raise_not_logged_in(message, log_message = nil) click to toggle source
# File lib/incline/extensions/action_controller_base.rb, line 522
def raise_not_logged_in(message, log_message = nil)
  log_authorize_failure message, log_message
  raise Incline::NotLoggedIn.new(message)
end
valid_user?() click to toggle source

Validates that the current user is authorized for the current request.

This method is called for every action except the :api action. For the :api action, this method will not be called until the actual requested action is performed.

One of four scenarios are possible:

  1. If the require_admin method applies to the current action, then a system administrator must be logged in.

    1. If a nobody is logged in, then the user will be redirected to the login url.

    2. If a system administrator is logged in, then access is granted.

    3. Non-system administrators will be redirected to the root url.

  2. If the require_anon method applies to the current action, then a user cannot be logged in.

    1. If a user is logged in, a warning message is set and the user is redirected to their account.

    2. If no user is logged in, then access is granted.

  3. If the allow_anon method applies to the current action, then access is granted.

  4. The action doesn't require a system administrator, but does require a valid user to be logged in.

    1. If nobody is logged in, then the user will be redirected to the login url.

    2. If a system administrator is logged in, then access is granted.

    3. If the action doesn't have any required permissions, then access is granted to any logged in user.

    4. If the action has required permissions and the logged in user shares at least one, then access is granted.

    5. Users without at least one required permission are redirected to the root url.

Two of the scenarios are configured at design time. If you use require_admin or allow_anon, they cannot be changed at runtime. The idea is that anything that allows anonymous access will always allow anonymous access during runtime and anything that requires admin access will always require admin access during runtime.

The third scenario is what would be used by most actions. Using the admin controller, which requires admin access, you can customize the permissions required for each available route. Using the users controller, admins can assign permissions to other users.

# File lib/incline/extensions/action_controller_base.rb, line 426
def valid_user? #:doc:
  if require_admin_for_request?
    authorize! true
  elsif require_anon_for_request?
    if logged_in?
      flash[:warning] = 'The specified action cannot be performed while logged in.'
      redirect_to incline.user_path(current_user)
    end
  elsif allow_anon_for_request?
    true
  else
    action = Incline::ActionSecurity.valid_items[self.class.controller_path, params[:action]]
    if action && action.groups.count > 0
      authorize! action.groups.pluck(:name)
    else
      authorize!
    end
  end
end