lib/timber/integrations/rack/user_context.rb in timber-2.0.24 vs lib/timber/integrations/rack/user_context.rb in timber-2.1.0.rc1
- old
+ new
@@ -1,73 +1,120 @@
+require "timber/integrations/rack/middleware"
+
module Timber
module Integrations
module Rack
- # Reponsible for adding the user context.
- class UserContext
- def initialize(app)
- @app = app
+ # This is a Rack middleware responsible for setting the user context.
+ # See {Timber::Contexts::User} for more information on the user context.
+ #
+ # We use a Rack middleware because we want to set the user context as early as
+ # possible, and before the initial incoming request log line:
+ #
+ # Started GET /welcome
+ #
+ # The above log line is logged in a request middleware, before it reaches
+ # the controller.
+ #
+ # If, for example, we set the user context in a controller, the log line above
+ # will not have the user context attached. This is because it is logged before
+ # the controller is executed. This is not ideal, and it's why we take a middleware
+ # approach here. If for some reason you cannot identify the user at the middleware
+ # level then setting it in the controller is perfectly fine, just be aware of the
+ # above downside.
+ #
+ # ## Authentication frameworks automatically detected:
+ #
+ # If you use any of the following authentication frameworks, Timber will
+ # automatically set the user context for you.
+ #
+ # * Devise, or any Warden based authentication strategy
+ # * Omniauth
+ # * Clearance
+ #
+ # Or, you can use your own custom authentication, see the {.custom_user_context}
+ # class method for more details.
+ #
+ # @note This middleware is automatically inserted for frameworks we support.
+ # Such as Rails. See {Timber::Frameworks} for a comprehensive list.
+ class UserContext < Middleware
+ class << self
+ # The custom user context allows you to hook in and set your own custom
+ # user context. This is used in situations where either:
+ #
+ # 1. Timber does not automatically support your authentication strategy (see module level docs)
+ # 2. You need to customize your authentication beyond Timber's defaults.
+ #
+ # @example Setting your own custom user context
+ # Timber::Integrations::Rack::UserContext.custom_user_hash = lambda do |rack_env|
+ # rach_env['my_custom_key'].user
+ # end
+ def custom_user_hash=(proc)
+ if proc && !proc.is_a?(Proc)
+ raise ArgumentError.new("The value passed to #custom_user_hash must be a Proc")
+ end
+
+ @custom_user_hash = proc
+ end
+
+ # Accessor method for {#custom_user_hash=}.
+ def custom_user_hash
+ @custom_user_hash
+ end
end
def call(env)
- debug { "#{self.class.name} - Starting user context" }
user_hash = get_user_hash(env)
if user_hash
- debug { "#{self.class.name} - User hash found: #{user_hash.inspect}" }
context = Contexts::User.new(user_hash)
CurrentContext.with(context) do
@app.call(env)
end
else
- debug { "#{self.class.name} - User hash not found" }
@app.call(env)
end
end
private
def get_user_hash(env)
- get_omniauth_user_hash(env) ||
- get_warden_user_hash(env) ||
+ # The order is relevant here. The 'warden' key can be set, but
+ # not return a user, in which case the user data might be in the omniauth
+ # data.
+ if self.class.custom_user_hash.is_a?(Proc)
+ debug { "Obtaining user context from the custom user hash" }
+ self.class.custom_user_hash.call(env)
+ elsif (auth_hash = env['omniauth.auth'])
+ debug { "Obtaining user context from the omniauth auth hash" }
+ get_omniauth_user_hash(auth_hash)
+ elsif env[:clearance] && env[:clearance].signed_in?
+ debug { "Obtaining user context from the clearance user" }
+ user = env[:clearance].current_user
+ get_user_object_hash(user)
+ elsif env['warden'] && (user = env['warden'].user)
+ debug { "Obtaining user context from the warden user" }
+ get_user_object_hash(user)
+ else
+ debug { "Could not locate any user data" }
nil
+ end
end
- def get_omniauth_user_hash(env)
- if env['omniauth.auth']
- debug { "#{self.class.name} - Omniauth hash present #{env['omniauth.auth'].inspect}" }
- auth_hash = env['omniauth.auth']
- info = auth_hash['info']
+ def get_omniauth_user_hash(auth_hash)
+ info = auth_hash['info']
- {
- id: auth_hash['uid'],
- name: info['name'],
- email: info['email']
- }
- else
- debug { "#{self.class.name} - Omniauth hash not present" }
- nil
- end
+ {
+ id: auth_hash['uid'],
+ name: info['name'],
+ email: info['email']
+ }
end
- def get_warden_user_hash(env)
- if env['warden']
- debug { "#{self.class.name} - Warden object present #{env['warden'].inspect}" }
- user = env['warden'].user
- if user
- debug { "#{self.class.name} - Warden user object #{env['warden'].user.inspect}" }
- id = try_user_id(user)
- name = try_user_name(user)
- email = try_user_email(user)
+ def get_user_object_hash(user)
+ id = try_user_id(user)
+ name = try_user_name(user)
+ email = try_user_email(user)
- if id || name || email
- debug { "#{self.class.name} - At least one warden user attribute was present" }
- {id: id, name: name, email: email}
- else
- debug { "#{self.class.name} - No warden user attributes were present" }
- nil
- end
- else
- debug { "#{self.class.name} - Warden user object not present, not logged in" }
- nil
- end
+ if id || name || email
+ {id: id, name: name, email: email}
else
nil
end
end
\ No newline at end of file