2017-12-21 12:36:29 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2010-02-19 03:52:04 -05:00
|
|
|
require "action_controller/metal"
|
|
|
|
|
2009-10-22 18:12:00 -04:00
|
|
|
module Devise
|
2009-11-21 21:49:23 -05:00
|
|
|
# Failure application that will be called every time :warden is thrown from
|
2016-08-15 15:51:11 -04:00
|
|
|
# any strategy or hook. It is responsible for redirecting the user to the sign
|
|
|
|
# in page based on current scope and mapping. If no scope is given, it
|
|
|
|
# redirects to the default_url.
|
2010-02-19 03:52:04 -05:00
|
|
|
class FailureApp < ActionController::Metal
|
|
|
|
include ActionController::UrlFor
|
|
|
|
include ActionController::Redirecting
|
2012-01-03 14:32:51 -05:00
|
|
|
|
2010-05-15 18:38:40 -04:00
|
|
|
include Rails.application.routes.url_helpers
|
2012-01-03 14:32:51 -05:00
|
|
|
include Rails.application.routes.mounted_helpers
|
2009-11-21 21:49:23 -05:00
|
|
|
|
2013-11-08 09:39:43 -05:00
|
|
|
include Devise::Controllers::StoreLocation
|
|
|
|
|
2014-02-25 11:42:55 -05:00
|
|
|
delegate :flash, to: :request
|
2010-04-03 05:43:31 -04:00
|
|
|
|
2009-10-22 18:12:00 -04:00
|
|
|
def self.call(env)
|
2011-11-07 05:47:28 -05:00
|
|
|
@respond ||= action(:respond)
|
|
|
|
@respond.call(env)
|
2009-11-21 21:49:23 -05:00
|
|
|
end
|
2009-11-16 11:31:09 -05:00
|
|
|
|
2015-08-18 19:42:56 -04:00
|
|
|
# Try retrieving the URL options from the parent controller (usually
|
2015-08-20 11:56:08 -04:00
|
|
|
# ApplicationController). Instance methods are not supported at the moment,
|
|
|
|
# so only the class-level attribute is used.
|
2010-02-19 03:52:04 -05:00
|
|
|
def self.default_url_options(*args)
|
2015-08-20 11:56:08 -04:00
|
|
|
if defined?(Devise.parent_controller.constantize)
|
|
|
|
Devise.parent_controller.constantize.try(:default_url_options) || {}
|
2012-01-02 14:00:38 -05:00
|
|
|
else
|
|
|
|
{}
|
|
|
|
end
|
2009-11-21 21:49:23 -05:00
|
|
|
end
|
|
|
|
|
2010-02-19 03:52:04 -05:00
|
|
|
def respond
|
2010-03-29 09:16:14 -04:00
|
|
|
if http_auth?
|
2010-04-03 05:43:31 -04:00
|
|
|
http_auth
|
|
|
|
elsif warden_options[:recall]
|
|
|
|
recall
|
2010-03-29 09:16:14 -04:00
|
|
|
else
|
2010-04-03 05:43:31 -04:00
|
|
|
redirect
|
2010-03-29 09:16:14 -04:00
|
|
|
end
|
2009-10-22 18:12:00 -04:00
|
|
|
end
|
2009-11-21 21:49:23 -05:00
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def http_auth
|
|
|
|
self.status = 401
|
2010-08-31 17:55:25 -04:00
|
|
|
self.headers["WWW-Authenticate"] = %(Basic realm=#{Devise.http_authentication_realm.inspect}) if http_auth_header?
|
2010-04-03 05:43:31 -04:00
|
|
|
self.content_type = request.format.to_s
|
|
|
|
self.response_body = http_auth_body
|
2010-04-01 11:30:55 -04:00
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def recall
|
2016-06-13 14:46:43 -04:00
|
|
|
header_info = if relative_url_root?
|
|
|
|
base_path = Pathname.new(relative_url_root)
|
2015-09-06 03:05:19 -04:00
|
|
|
full_path = Pathname.new(attempted_path)
|
|
|
|
|
2016-06-13 14:46:43 -04:00
|
|
|
{ "SCRIPT_NAME" => relative_url_root,
|
2015-08-18 19:42:56 -04:00
|
|
|
"PATH_INFO" => '/' + full_path.relative_path_from(base_path).to_s }
|
2015-09-06 03:05:19 -04:00
|
|
|
else
|
2015-08-18 19:42:56 -04:00
|
|
|
{ "PATH_INFO" => attempted_path }
|
|
|
|
end
|
|
|
|
|
|
|
|
header_info.each do | var, value|
|
|
|
|
if request.respond_to?(:set_header)
|
|
|
|
request.set_header(var, value)
|
|
|
|
else
|
2016-06-18 09:20:08 -04:00
|
|
|
request.env[var] = value
|
2015-08-18 19:42:56 -04:00
|
|
|
end
|
2015-09-06 03:05:19 -04:00
|
|
|
end
|
|
|
|
|
2015-04-21 10:27:44 -04:00
|
|
|
flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
|
2015-08-18 19:42:56 -04:00
|
|
|
self.response = recall_app(warden_options[:recall]).call(request.env)
|
2010-03-29 09:16:14 -04:00
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def redirect
|
|
|
|
store_location!
|
2015-04-21 10:27:44 -04:00
|
|
|
if is_flashing_format?
|
|
|
|
if flash[:timedout] && flash[:alert]
|
|
|
|
flash.keep(:timedout)
|
|
|
|
flash.keep(:alert)
|
|
|
|
else
|
|
|
|
flash[:alert] = i18n_message
|
|
|
|
end
|
2012-01-24 07:58:57 -05:00
|
|
|
end
|
2010-07-05 09:22:44 -04:00
|
|
|
redirect_to redirect_url
|
2010-03-29 09:16:14 -04:00
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
protected
|
|
|
|
|
2013-10-24 11:29:53 -04:00
|
|
|
def i18n_options(options)
|
|
|
|
options
|
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def i18n_message(default = nil)
|
2012-01-24 07:58:57 -05:00
|
|
|
message = warden_message || default || :unauthenticated
|
2010-04-03 05:43:31 -04:00
|
|
|
|
|
|
|
if message.is_a?(Symbol)
|
2013-10-24 10:46:45 -04:00
|
|
|
options = {}
|
|
|
|
options[:resource_name] = scope
|
|
|
|
options[:scope] = "devise.failure"
|
|
|
|
options[:default] = [message]
|
2020-09-29 14:34:33 -04:00
|
|
|
options[:locale] = warden_options[:locale]
|
2014-10-27 20:32:19 -04:00
|
|
|
auth_keys = scope_class.authentication_keys
|
2016-04-26 10:17:03 -04:00
|
|
|
keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
|
2020-09-29 14:34:33 -04:00
|
|
|
options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector", locale: warden_options[:locale]))
|
2013-10-24 11:29:53 -04:00
|
|
|
options = i18n_options(options)
|
2013-10-24 10:46:45 -04:00
|
|
|
|
2019-12-26 19:44:53 -05:00
|
|
|
I18n.t(:"#{scope}.#{message}", **options)
|
2010-03-29 09:16:14 -04:00
|
|
|
else
|
|
|
|
message.to_s
|
|
|
|
end
|
2010-04-03 05:43:31 -04:00
|
|
|
end
|
2010-03-29 09:16:14 -04:00
|
|
|
|
2010-07-05 09:22:44 -04:00
|
|
|
def redirect_url
|
2012-01-24 07:58:57 -05:00
|
|
|
if warden_message == :timeout
|
2015-04-21 10:27:44 -04:00
|
|
|
flash[:timedout] = true if is_flashing_format?
|
2013-05-19 08:45:11 -04:00
|
|
|
|
|
|
|
path = if request.get?
|
|
|
|
attempted_path
|
|
|
|
else
|
|
|
|
request.referrer
|
|
|
|
end
|
|
|
|
|
2014-03-31 12:32:45 -04:00
|
|
|
path || scope_url
|
2012-01-24 07:58:57 -05:00
|
|
|
else
|
2014-03-31 12:32:45 -04:00
|
|
|
scope_url
|
2012-01-24 07:58:57 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-12 07:07:57 -05:00
|
|
|
def route(scope)
|
|
|
|
:"new_#{scope}_session_url"
|
|
|
|
end
|
|
|
|
|
2014-03-31 12:32:45 -04:00
|
|
|
def scope_url
|
2011-09-08 02:32:05 -04:00
|
|
|
opts = {}
|
2016-05-21 10:55:56 -04:00
|
|
|
|
|
|
|
# Initialize script_name with nil to prevent infinite loops in
|
|
|
|
# authenticated mounted engines in rails 4.2 and 5.0
|
|
|
|
opts[:script_name] = nil
|
|
|
|
|
2015-02-12 07:07:57 -05:00
|
|
|
route = route(scope)
|
2016-05-21 10:55:56 -04:00
|
|
|
|
2011-09-08 02:32:05 -04:00
|
|
|
opts[:format] = request_format unless skip_format?
|
2012-05-09 17:41:05 -04:00
|
|
|
|
2015-03-30 19:18:05 -04:00
|
|
|
router_name = Devise.mappings[scope].router_name || Devise.available_router_name
|
|
|
|
context = send(router_name)
|
2012-01-03 14:32:51 -05:00
|
|
|
|
2019-01-23 12:32:29 -05:00
|
|
|
if relative_url_root?
|
|
|
|
opts[:script_name] = relative_url_root
|
2019-01-23 12:41:30 -05:00
|
|
|
|
|
|
|
# We need to add the rootpath to `script_name` manually for applications that use a Rails
|
|
|
|
# version lower than 5.1. Otherwise, it is going to generate a wrong path for Engines
|
2019-11-28 16:13:47 -05:00
|
|
|
# that use Devise. Remove it when the support of Rails 5.0 is dropped.
|
2019-04-03 09:44:35 -04:00
|
|
|
elsif root_path_defined?(context) && !rails_51_and_up?
|
2019-01-23 12:32:29 -05:00
|
|
|
rootpath = context.routes.url_helpers.root_path
|
2019-01-23 12:41:30 -05:00
|
|
|
opts[:script_name] = rootpath.chomp('/') if rootpath.length > 1
|
2019-01-23 12:32:29 -05:00
|
|
|
end
|
|
|
|
|
2012-01-03 14:32:51 -05:00
|
|
|
if context.respond_to?(route)
|
|
|
|
context.send(route, opts)
|
2014-03-31 12:32:45 -04:00
|
|
|
elsif respond_to?(:root_url)
|
|
|
|
root_url(opts)
|
2012-01-02 16:31:30 -05:00
|
|
|
else
|
|
|
|
"/"
|
2011-01-07 19:40:21 -05:00
|
|
|
end
|
2010-07-05 09:22:44 -04:00
|
|
|
end
|
|
|
|
|
2011-04-29 08:11:47 -04:00
|
|
|
def skip_format?
|
|
|
|
%w(html */*).include? request_format.to_s
|
|
|
|
end
|
|
|
|
|
2016-08-15 15:51:11 -04:00
|
|
|
# Choose whether we should respond in an HTTP authentication fashion,
|
2010-08-31 17:55:25 -04:00
|
|
|
# including 401 and optional headers.
|
|
|
|
#
|
2016-08-15 15:51:11 -04:00
|
|
|
# This method allows the user to explicitly disable HTTP authentication
|
|
|
|
# on AJAX requests in case they want to redirect on failures instead of
|
|
|
|
# handling the errors on their own. This is useful in case your AJAX API
|
2010-08-31 17:55:25 -04:00
|
|
|
# is the same as your public API and uses a format like JSON (so you
|
|
|
|
# cannot mark JSON as a navigational format).
|
2010-04-03 05:43:31 -04:00
|
|
|
def http_auth?
|
2010-08-31 11:44:19 -04:00
|
|
|
if request.xhr?
|
|
|
|
Devise.http_authenticatable_on_xhr
|
|
|
|
else
|
2011-04-17 13:43:54 -04:00
|
|
|
!(request_format && is_navigational_format?)
|
2010-08-31 11:44:19 -04:00
|
|
|
end
|
2010-03-29 09:16:14 -04:00
|
|
|
end
|
|
|
|
|
2016-08-15 15:51:11 -04:00
|
|
|
# It doesn't make sense to send authenticate headers in AJAX requests
|
2010-08-31 17:55:25 -04:00
|
|
|
# or if the user disabled them.
|
|
|
|
def http_auth_header?
|
2014-09-24 09:48:46 -04:00
|
|
|
scope_class.http_authenticatable && !request.xhr?
|
2010-08-31 17:55:25 -04:00
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def http_auth_body
|
2011-02-15 04:26:28 -05:00
|
|
|
return i18n_message unless request_format
|
|
|
|
method = "to_#{request_format}"
|
2011-04-17 01:47:54 -04:00
|
|
|
if method == "to_xml"
|
2014-02-25 11:42:55 -05:00
|
|
|
{ error: i18n_message }.to_xml(root: "errors")
|
2011-04-17 01:47:54 -04:00
|
|
|
elsif {}.respond_to?(method)
|
2014-02-25 11:42:55 -05:00
|
|
|
{ error: i18n_message }.send(method)
|
2011-04-17 01:47:54 -04:00
|
|
|
else
|
|
|
|
i18n_message
|
|
|
|
end
|
2010-02-19 03:52:04 -05:00
|
|
|
end
|
|
|
|
|
2010-09-25 05:21:51 -04:00
|
|
|
def recall_app(app)
|
|
|
|
controller, action = app.split("#")
|
2011-04-16 07:30:15 -04:00
|
|
|
controller_name = ActiveSupport::Inflector.camelize(controller)
|
|
|
|
controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
|
|
|
|
controller_klass.action(action)
|
2010-04-01 11:30:55 -04:00
|
|
|
end
|
|
|
|
|
2010-02-19 03:52:04 -05:00
|
|
|
def warden
|
2016-06-18 09:20:08 -04:00
|
|
|
request.respond_to?(:get_header) ? request.get_header("warden") : request.env["warden"]
|
2010-02-19 03:52:04 -05:00
|
|
|
end
|
2009-11-21 21:49:23 -05:00
|
|
|
|
2010-02-19 03:52:04 -05:00
|
|
|
def warden_options
|
2016-06-18 09:20:08 -04:00
|
|
|
request.respond_to?(:get_header) ? request.get_header("warden.options") : request.env["warden.options"]
|
2009-11-21 21:49:23 -05:00
|
|
|
end
|
|
|
|
|
2012-01-24 07:58:57 -05:00
|
|
|
def warden_message
|
|
|
|
@message ||= warden.message || warden_options[:message]
|
|
|
|
end
|
|
|
|
|
2010-04-03 05:43:31 -04:00
|
|
|
def scope
|
2010-07-14 11:55:14 -04:00
|
|
|
@scope ||= warden_options[:scope] || Devise.default_scope
|
2010-04-03 05:43:31 -04:00
|
|
|
end
|
|
|
|
|
2014-09-24 09:48:46 -04:00
|
|
|
def scope_class
|
|
|
|
@scope_class ||= Devise.mappings[scope].to
|
|
|
|
end
|
|
|
|
|
2010-03-11 12:39:32 -05:00
|
|
|
def attempted_path
|
|
|
|
warden_options[:attempted_path]
|
|
|
|
end
|
|
|
|
|
2016-08-15 15:51:11 -04:00
|
|
|
# Stores requested URI to redirect the user after signing in. We can't use
|
|
|
|
# the scoped session provided by warden here, since the user is not
|
|
|
|
# authenticated yet, but we still need to store the URI based on scope, so
|
|
|
|
# different scopes would never use the same URI to redirect.
|
2010-04-03 05:43:31 -04:00
|
|
|
def store_location!
|
2013-11-08 09:39:43 -05:00
|
|
|
store_location_for(scope, attempted_path) if request.get? && !http_auth?
|
2009-11-21 21:49:23 -05:00
|
|
|
end
|
2012-01-02 15:00:55 -05:00
|
|
|
|
|
|
|
def is_navigational_format?
|
|
|
|
Devise.navigational_formats.include?(request_format)
|
|
|
|
end
|
|
|
|
|
2015-04-21 10:27:44 -04:00
|
|
|
# Check if flash messages should be emitted. Default is to do it on
|
|
|
|
# navigational formats
|
|
|
|
def is_flashing_format?
|
2018-11-13 12:26:00 -05:00
|
|
|
request.respond_to?(:flash) && is_navigational_format?
|
2015-04-21 10:27:44 -04:00
|
|
|
end
|
|
|
|
|
2012-01-02 15:00:55 -05:00
|
|
|
def request_format
|
|
|
|
@request_format ||= request.format.try(:ref)
|
|
|
|
end
|
2016-06-13 14:46:43 -04:00
|
|
|
|
|
|
|
def relative_url_root
|
|
|
|
@relative_url_root ||= begin
|
|
|
|
config = Rails.application.config
|
|
|
|
|
|
|
|
config.try(:relative_url_root) || config.action_controller.try(:relative_url_root)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def relative_url_root?
|
|
|
|
relative_url_root.present?
|
|
|
|
end
|
2018-03-25 07:34:59 -04:00
|
|
|
|
|
|
|
ActiveSupport.run_load_hooks(:devise_failure_app, self)
|
2019-01-23 12:41:30 -05:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def root_path_defined?(context)
|
2019-02-11 08:00:56 -05:00
|
|
|
defined?(context.routes) && context.routes.url_helpers.respond_to?(:root_path)
|
2019-01-23 12:41:30 -05:00
|
|
|
end
|
|
|
|
|
2019-04-03 09:44:35 -04:00
|
|
|
def rails_51_and_up?
|
2019-05-05 06:44:44 -04:00
|
|
|
Rails.gem_version >= Gem::Version.new("5.1")
|
2019-01-23 12:41:30 -05:00
|
|
|
end
|
2009-10-22 18:12:00 -04:00
|
|
|
end
|
|
|
|
end
|