Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does ":event => :authentication" do?

This is actually a pretty simple question but I can't seem to find the answer. In the Omniauth Overview on Github, there is actually an explanation but I don't get it:

We pass the :event => :authentication to the sign_in_and_redirect method
to force all authentication callbacks to be called.

I already have authentication working using an action similar to this one:

def facebook
  authenticator = UserAuthenticator.new(request.env["omniauth.auth"], current_user)

  if authenticator.user_authenticated?
    sign_in_and_redirect authenticator.user, :event => :authentication
  else
    session["devise.oauth_data"] = request.env["omniauth.auth"]
    redirect_to new_user_registration_url
  end
end

All I really want to know is what good is :event => :authentication for?

like image 861
Ashitaka Avatar asked Feb 10 '12 01:02

Ashitaka


2 Answers

I just would like to help to figure out the answer. I tracked the source code myself, that help me understand how the :event => :authentication parameter works. I hope it also helps you.

So your question is why

pass the :event => :authentication to the sign_in_and_redirect method to force all authentication callbacks to be called.

then, we can track the definition.

# Sign in a user and tries to redirect first to the stored location and
# then to the url specified by after_sign_in_path_for. It accepts the same
# parameters as the sign_in method.
def sign_in_and_redirect(resource_or_scope, *args)
  options  = args.extract_options!
  scope    = Devise::Mapping.find_scope!(resource_or_scope)
  resource = args.last || resource_or_scope
  sign_in(scope, resource, options)
  redirect_to after_sign_in_path_for(resource)
end

and then sign_in define in devise:

# All options given to sign_in is passed forward to the set_user method in warden.
# The only exception is the :bypass option, which bypass warden callbacks and stores
# the user straight in session. This option is useful in cases the user is already
# signed in, but we want to refresh the credentials in session.
#
# Examples:
#
#   sign_in :user, @user                      # sign_in(scope, resource)
#   sign_in @user                             # sign_in(resource)
#   sign_in @user, :event => :authentication  # sign_in(resource, options)
#   sign_in @user, :bypass => true            # sign_in(resource, options)
#
def sign_in(resource_or_scope, *args)
  options  = args.extract_options!
  scope    = Devise::Mapping.find_scope!(resource_or_scope)
  resource = args.last || resource_or_scope

  expire_session_data_after_sign_in!

  if options[:bypass]
    warden.session_serializer.store(resource, scope)
  elsif warden.user(scope) == resource && !options.delete(:force)
    # Do nothing. User already signed in and we are not forcing it.
    true
  else
    warden.set_user(resource, options.merge!(:scope => scope))
  end
end

Okay, so :event => :authentication now is passed to warden#set_user, Then your question become why

pass the :event => :authentication to the sign_in_and_redirect method to force all authentication callbacks to be called.

# Manually set the user into the session and auth proxy
#
# Parameters:
#   user - An object that has been setup to serialize into and out of the session.
#   opts - An options hash.  Use the :scope option to set the scope of the user, set the :store option to false to skip serializing into the session, set the :run_callbacks to false to skip running the callbacks (the default is true).
#
# :api: public
def set_user(user, opts = {})
  scope = (opts[:scope] ||= @config.default_scope)

  # Get the default options from the master configuration for the given scope
  opts = (@config[:scope_defaults][scope] || {}).merge(opts)
  opts[:event] ||= :set_user
  @users[scope] = user

  if opts[:store] != false && opts[:event] != :fetch
    options = env[ENV_SESSION_OPTIONS]
    options[:renew] = true if options
    session_serializer.store(user, scope)
  end

  run_callbacks = opts.fetch(:run_callbacks, true)
  manager._run_callbacks(:after_set_user, user, self, opts) if run_callbacks

  @users[scope]
end

opts[:event] can be [:set_user, :fetch, :authentication]

# Hook to _run_callbacks asserting for conditions.
def _run_callbacks(kind, *args) #:nodoc:
  options = args.last # Last callback arg MUST be a Hash

  send("_#{kind}").each do |callback, conditions|
    invalid = conditions.find do |key, value|
      value.is_a?(Array) ? !value.include?(options[key]) : (value != options[key])
    end

    callback.call(*args) unless invalid
  end
end

# A callback hook set to run every time after a user is set.
# This callback is triggered the first time one of those three events happens
# during a request: :authentication, :fetch (from session) and :set_user (when manually set).
# You can supply as many hooks as you like, and they will be run in order of decleration.
#
# If you want to run the callbacks for a given scope and/or event, you can specify them as options.
# See parameters and example below.
#
# Parameters:
# <options> Some options which specify when the callback should be executed
#   scope  - Executes the callback only if it maches the scope(s) given
#   only   - Executes the callback only if it matches the event(s) given
#   except - Executes the callback except if it matches the event(s) given
# <block> A block where you can set arbitrary logic to run every time a user is set
#   Block Parameters: |user, auth, opts|
#     user - The user object that is being set
#     auth - The raw authentication proxy object.
#     opts - any options passed into the set_user call includeing :scope
#
# Example:
#   Warden::Manager.after_set_user do |user,auth,opts|
#     scope = opts[:scope]
#     if auth.session["#{scope}.last_access"].to_i > (Time.now - 5.minutes)
#       auth.logout(scope)
#       throw(:warden, :scope => scope, :reason => "Times Up")
#     end
#     auth.session["#{scope}.last_access"] = Time.now
#   end
#
#   Warden::Manager.after_set_user :except => :fetch do |user,auth,opts|
#     user.login_count += 1
#   end
#
# :api: public
def after_set_user(options = {}, method = :push, &block)
  raise BlockNotGiven unless block_given?

  if options.key?(:only)
    options[:event] = options.delete(:only)
  elsif options.key?(:except)
    options[:event] = [:set_user, :authentication, :fetch] - Array(options.delete(:except))
  end

  _after_set_user.send(method, [block, options])
end

so,

# after_authentication is just a wrapper to after_set_user, which is only invoked
# when the user is set through the authentication path. The options and yielded arguments
# are the same as in after_set_user.
#
# :api: public
def after_authentication(options = {}, method = :push, &block)
  after_set_user(options.merge(:event => :authentication), method, &block)
end
like image 161
Race Avatar answered Oct 22 '22 01:10

Race


Passing :event => :authentication causes Warden (underlying Devise) to trigger any callbacks defined with:

Warden::Manager.after_authentication do |user,auth,opts|
  # Code triggered by authorization here, for example:
  user.last_login = Time.now
end

If you're not using any after_authentication callbacks, and you're confident your libraries aren't either, then it's not of any immediate use to you. Since it is an authentication event, I'd leave it in, now that you know what it's potentially useful for.

like image 17
Cyberfox Avatar answered Oct 22 '22 01:10

Cyberfox