Setting current_user in pg_audit_log - ruby-on-rails

I'd like to use pg_audit_log for logging in a rails app. The audit log must not only show the columns that have changed, but also the user who made those changes. The docs don't show how to do this, but after looking through the pg_audit_log source (postgresql_adapter.rb) I see it reads user information from a thread local variable, ala:
current_user = Thread.current[:current_user]
I've considered setting/unsetting this in before and after filters like so:
Thread.current[:current_user] = current_user
(using the current_user helper method in the controller to get the currently logged in user), but that seems dangerous. I'm now spending time trying to understand how the rails request cycle and threads interact, to get a better feel for just how dangerous. In the mean time, I was curious if any SO users currently using pg_audit_log have solved the need to log the user_id and user_unique_name to the log tables each time the user makes a change to a record.

Setting the current user the way you describe is a common way to do it. See, for example, http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
Some example code could look like:
# in your model
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(user)
Thread.current[:current_user] = user if user.nil? || user.is_a?(User)
end
end
# in your controller
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = user_signed_in? ? current_user : nil
end
end

Relying on the Thread.current hash to provide model-level access to objects managed by the controller is indeed controversial. For example, see the following:
Safety of Thread.current[] usage in rails
It is worrisome that this particular feature is undocumented in the pg_audit_log gem.
Suppose you had not actively explored the gem's source code, and suppose you had independently decided to define Thread.current[:current_user] = something in your own application, for your own purpose. In that case, pg_audit_log would audit that object, without your knowledge.
Granted, the name current_user is so universally accepted to mean the currently logged-on user as defined by authentication routines that it's difficult to imagine this potential bug as a concrete problem, but from a design standpoint? Ouch.
On the other hand, since you know what you are doing, ensuring that Thread.current[:current_user] is set/unset at the beginning/end of each and every response cycle should make the process safe. At least that's what I gather from reading lots of posts on the topic.
Cheers, Giuseppe

Related

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

Preferred way to use sessions to avoid hitting the database in rails

I try to optimise a Rails app with big load that currently hit the databsae on every request. I try now to optimise this by saving some info on the session so I don't need to go the database every time. I'm currently doing something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
else
session[:audience] ||= 'general'
end
end
And then calling session[:audience] anywhere on my controller and views. Seems fine except that I'm seeing on the New Relic logs that sometimes the session is not set and therefore the app get a nasty nil.
Im thinking better I should use instance variables, maybe more something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
end
#audience = session[:audience]
#audience = 'general' if #audience.empty?
end
And then calling #audience in my app.
Is this correct? I would like to make sure I'm used the preferred approach to this.
I think the standard approach here would be to use a helper method on ApplicationContoller:
class ApplicationController < ActionController::Base
private
def current_audience
#current_audience ||= current_user.audience
end
helper_method :current_audience
end
This will work pretty much exactly like the current_user helper method in your controllers and views. Depending on the specifics of your application, you may want to add some more robust nil handling, but this is the basic idea.

Thread-safe Rails controller actions - setting instance variables?

I have to write a threaded Rails app because I am running it atop of Neo4j.rb, which embeds a Neo4j graph database inside the Rails process, and thus I have to serve multiple requests from the same process. Yeah, it'd be cool if connecting to a Neo4j database worked like SQL databases, but it doesn't, so I'll quit complaining and just use it.
I'm quite worried about the implications of writing concurrent code (as I should be), and just need some advice on how to handle common a common scenario - a controller sets an instance variable or a variable in the session hash, then some stuff happens. Consider the following crude code to demonstrate what I mean:
# THIS IS NOT REAL PRODUCTION CODE
# I don't do this in real life, it is just to help me ask my question, I
# know about one-way hashing, etc.!
class SessionsController
def create
user = User.find_by_email_and_password(params[:email], params[:password])
raise 'auth error' unless user
session[:current_user_id] = user.id
redirect_to :controller => 'current_user', :action => 'show'
end
end
class CurrentUserController
def show
#current_user = User.find(session[:current_user_id])
render :action => :show # .html.erb file that uses #current_user
end
end
The question: Are there any race conditions in this code?
In SessionsController, are the session hash and the params hash thread-local? Say the same browser session makes multiple requests to /sessions#create (to borrow Rails route syntax) with different credentials, the user that is logged in should be the request that hit the line session[:current_user_id] = user.id last? Or should I wrap a mutex lock around the controller action?
In the CurrentUserController, if the show action is hit simultaneously by two requests with different sessions, will the same #current_user variable be set by both? I.e. will the first request, as it is processing the .html.erb file, find that it's #current_user instance variable has suddenly been changed by the second thread?
Thanks
Each request gets a new instance of your controller. As a consequence controller instance variables are thread safe. params and session are also backed by controller instance variables (or the request object itself) and so are also safe.
It's important to know what is shared between threads and what isn't.
Now back to your specific example. Two requests hit CurrentUserController#show simultaneously, hence they are handled by two concurrent threads. The key here is that each thread has its own instance of CurrentUserController, so there are two #current_user variables which don't interfere. So there's no race condition around #current_user.
An example of race condition would be this:
class ApplicationController < ActionController::Base
before_each :set_current_user
cattr_accessor :current_user
def set_current_user
self.class.current_user = User.find_by_id(session[:current_user_id])
end
end
# model
class LogMessage < ActiveRecord::Base
belongs_to :user
def self.log_action(attrs)
log_message = new(attrs)
log_message.user = ApplicationController.current_user
log_message.save
end
end
On more general note, because of GIL (Global Interpreter Lock) benefits from using threads in MRI ruby are rather limited. There are implementation which are free from GIL (jruby).

Rails current_user best practice

I'm on a dilema, a have a ton of objects associated to the current_user on my app. And and don't know if in my controllers i keep using the IDs to find these objects or put directly the current_user + object.
Exemple:
class HousesController < ApplicationController
def show
#house = House.find(params[:id]) **or?** #house = current_user.house
end
def edit
#house = House.find(params[:id]) **or?** #house = current_user.house
end
end
And this going on and on. thank's in advance
If you use House.find(params[:id]) you have a potential security hole, as a given user could simply change the number in the url and access the house for a different user. So if you go this route, you have to add something to protect unathorized access.
OTOH, current_user.house keeps them on their own house, but needs alternate code for admin functions.
For simple applications, you can do this by hand, but for larger applications, you might want to consider authorization frameworks such as cancan or declarative_authorization where you can more easily define the permissions.
I use decl_auth myself, and all my controllers either use its method of loading the resource with filter_resource_access (loads the appropriate resource or throws and error if not allowed) or by hand with House.with_permissions_to(:index) which will only give me a house if I have permission to load it.
As always, Railscasts say it best: cancan and declarative authorization.

how to proper cache a variable in rails

Hi I have a rails App which displays always the company name on each page.
Since a logged in user can have multiple companies she belongs to.
User and companies are stored in the db.
I use authlogic for the user management.
Now I do not want to hit the database on every postback or page change
What would be best practise to chache/store the company until the logged in users changes or the user selects a different company? Something like global instance vars for a given user.
I started with this in my application_controller
def current_company
return #current_company if defined?(#current_company)
#current_company = Account.includes(:users).where(:users =>current_company)
end
and I realized that I am still hitting the db...
Is the session the recommended way or what would be best practice for this...
Thanks for the help in advance
||= way:
def current_company
#current_company ||= Account.includes(:users).where(:users =>current_company)
end
memoize way:
def current_company
Account.includes(:users).where(:users =>current_company)
end
memoize :current_company
Differences between this method and normal memoization with ||=
http://apidock.com/rails/ActiveSupport/memoize#447-Differences-between-normal-or-assign-operator
#tadman, you are right but from my point of view depends how complex its the method that you are trying to "cache". For simple cases I prefer ||=
I think this is what you're looking for
https://github.com/nkallen/cache-money

Resources