How can I use Devise methods in my ConnectionAdapter callback? - ruby-on-rails

I have a rails 5.1 app that's using Devise to handle authentication with my User model. This app has an Oracle database backend that requires setting a system context variable with the logged-in user prior to executing any queries, so I was hoping to do that in the :checkout callback for the ConnectionAdapter.
class ApplicationController < ActionController::Base
before_action :log_user
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.set_callback :checkout, :after do
# Would like to get the logged-in user's username here so I can apply
# it to the oracle sys_context.
# The below throws "undefined method 'user_signed_in?'"
username = current_user.username if user_signed_in?
end
def log_user
# When in this method, user_signed_in? and current_user work fine.
puts "User is #{current_user.username}" if user_signed_in?
end
end
The user_signed_in? method isn't found when run in the :checkout callback block, though it's generally available in the controller. Why?
Also, current_user within the block seems to evaluate to the current_user method defined within the ConnectionAdapter rather than the one defined by Devise. How can I get access to Devise's current_user?
How can I use these Devise-provided methods from within this callback?

You can't use the checkout callback, at the point that it's executed, it has no connection to the controller context. The fact that you've defined it here in your ApplicationController is irrelevant to the context it's actually executed in.
You will need to set the connection option in the before_action so you're running in the controller context. Something like:
before_action :set_user_context
def set_user_context
if current_user
ApplicationRecord.connection.execute "DBMS_SESSION.SET_CONTEXT('whatever', 'goes', 'here', '#{current_user.username}')"
end
end
...or something like that. Note that you might want to add a checkin callback to clear the value when the connection is finished with.
Btw, I answered a nearly identical question a few days ago: https://stackoverflow.com/a/54837596/152786 Different commands though, but might help.

Related

Devise's current_user nil in ApplicationController but not in a different controller (using Simple Token Authentication)

I have a Rails 3.2.22 app running in production for +1 year which uses Devise to authenticate users.
I'm trying to implement token authentication, so I can send transactional e-mails with URL params that can log in the user automatically, using a Gem named Simple Token Authentication https://github.com/gonzalo-bulnes/simple_token_authentication
After following all the instructions, I replaced before_filter :authenticate_user! in my controllers with acts_as_token_authentication_handler_for User.
The gem has integration with and a default fallback to Devise, so devise doesn't need to be called in the controllers anymore; if the token is missing from the params (or wrong), Devise will take over.
In my tests, if I add this line to ApplicationController, everything works fine and I can log in users using the authentication_token= secret the gem generates.
But I don't need auth for ApplicationController, I need it for other controllers (like DashboardController), url being /dashboard
If I put acts_as_token_authentication_handler_for User in that controller (replacing Devise's call), I get the most bizarre of situations.
Using binding.pry, I can confirm that current_user is correctly set during the loading of the template.
But there comes a point in the template where it uses #last_emails, which is defined inside a method in ApplicationController.
Using binding.pry, I can confirm current_user is nil there.
This is the code:
class DashboardController < ApplicationController
layout 'material'
acts_as_token_authentication_handler_for User
And in ApplicationController:
class ApplicationController < ActionController::Base
layout 'omega'
before_filter :populate_last_contacts_for_menu
private
def populate_last_contacts_for_menu
if current_user
#last_contacts = Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
end
end
Funny thing is: using binding.pry, like I said, I can check that current_user is defined in the template (which means sign_in was a success). It even is defined in the better errors console. But, if I go to homepage, I see that user is not logged in ...
I've looked all over the web for this: read all the issues inside the Gem's github and all posts in SO about current_user being nil, but no light at all.
My devise_for :users is not inside any scope in routes.rb and, as I said, I have many calls to current_user all over the app and this is the first time I have issues with Devise.
When you call the acts_as_token_authentication_handler_for directive in the DashboardController it declares some before_filters for the controller to authenticate a user.
But the problem is that when you inherit rails controllers, at first, filters of a parent controller are executed, then filters of a child controller.
The parent controller is ApplicationController. At the moment when it's populate_last_contacts_for_menu filter is called, the user is not authentacated, because the authenticating filters given by the acts_as_token_authentication_handler_for directive have not called yet, they are declared in the child controller.
Possible solutions:
1) Try to append the populate_last_contacts_for_menu filter:
append_before_filter :populate_last_contacts_for_menu
I am not sure it will work in your case, but you can try and find it out.
2) Call the acts_as_token_authentication_handler_for directive in the ApplicationControoler and somehow skip it for the controllers that don't need it. (I don't like this way, but it may help if the first one will not work. )
3) Move the populate_last_contacts_for_menu filter logic into helpers. I think it is the best solution. This logic doesn't belong to a controller. When requests are not 'get', this filter executes for nothing, because you don't need to render views in that case.
module ApplicationHelper
def last_contacts
#last_contacts ||= if signed_in?
Contact.where("user_id" => current_user.id).where('blocked != ? or blocked is null', true).last(10).reverse
else
[]
end
end
...
end
# View:
<% if last_contacts.present? %>
....
<% end %>

CanCan explanation of load_and_authorize_resource

I would know how the load_and_authorize_resource works inside.
I searched the github page Link and tried to undestand , but i didn't find nothing usefull. I only understand that load_and_authorize_resource is like a before_filter and it loads (in some way) the ability that we have written in ability.rb
I would know better how this is possible. I mean, i don't want to study ALL the gem, but i want just to see how cancan load the ability of a resource in a controller and if the load_and_authorize_resource is really a sort of before_filter.
disclaimer: for the sake of simplicity, I omit some calls to short inner methods intentionally. The full chain of calling can be obtained by following load_and_authorize_resource method definition and so forth.
As stated in documentation, load_and_authorize_resource sets up a before_filter...
# cancan/lib/cancan/controller_additions.rb
def load_and_authorize_resource(*args)
cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
end
...which calls two methods: load_resource and authorize_resource.
# cancan/lib/cancan/controller_resource.rb
def load_and_authorize_resource
load_resource
authorize_resource
end
To get the idea of their behaviour we're going to look at both of them closely.
Based on params hash which was passed to your controller action, load_resource makes a decision on whether it should obtain a new instance of a class (e.g. Post.new) or find a particular instance based on params[:id] (e.g. Post.find(params[:id])). That instance (or a collection of instances for actions like index) is assigned to corresponding instance variable of your controller action.
# cancan/lib/cancan/controller_resource.rb
def load_resource
unless skip?(:load)
if load_instance?
# here you have obtained your object, e.g. Post with id=5
# and placed it into cancan resource_instance variable.
# it has automatically set up #post instance variable for you
# in your action
self.resource_instance ||= load_resource_instance
elsif load_collection?
self.collection_instance ||= load_collection
end
end
end
Later on, authorize_resource gets called. Its inner logics syntax should be familiar to you: checking abilities by hands looks just the same as what happens inside of this method. Basically you take a resource_instance obtained at the previous step, params[:action] which is the name of a current action, and check if particular action can be accessed for given object(s).
# cancan/lib/cancan/controller_resource.rb
def authorize_resource
unless skip?(:authorize)
# similar to what happens when you call authorize!(:show, #post)
#controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
end
end
As long as raising exceptions inside of before_filter stops controller action from being executed, failing to pass authorization here gets you redirected to your application's home url, shown 500 error page or whatever behaviour you defined for CanCan::AccessDenied handling.
On the other hand, in case you've passed authorization successfully, your action code gets executed. Now you've got access to instance variable (e.g. #post) which has been set up by CanCan at load_resource step.

In Ruby on Rails Devise: How to Plug Some Code to Handle the Current User When S/he Logs Out

Basically, I want to log something about the current user when s/he logs out.
I am trying to override after_sign_out_path_for:
def after_sign_out_path_for(user)
# Notice that differently from +after_sign_in_path_for+ this method
# receives a symbol with the scope, and not the resource.
# puts current_user.id
new_user_session_path
end
But the method current_user sometimes it returns nil, and from this ticket (https://github.com/plataformatec/devise/pull/2022), it seems current_user is not available in after_sign_out_path_for.
What should I do? Do I have to override other methods? Like: sign_out_and_redirect? Are the any cleaner way to do?
You could try something like:
before_filter :log_user_logout, only: [:destroy]
and access current_user within that method

Access user properties through a helper method in rails

I wrote this helper method:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
end
To which i should be able, in my mind, to do current_user.role == 'some role' but when I do that it spazzes out saying "undefined method role for nil:NilClass" now does that mean the role column is empty and has nothing it in or that the user object is empty? because I assure you I am logged in, I exist in the database and .... the role field in the database is empty how ever.
Update I should probably state that doing User.role == 'admin' works, as their is a role attribute in the database, or well column. Why can't I do .role on current_user?
Based on this error, you can be certain that current_user is returning the value nil. So the issue isn't the method role. You should note that User.role is a class method on the model User, so it is not calling a method on one particular user. current_user.role on the other hand is an instance method for one particular user, the user that is signed in.
I would put the following right above the method that is throwing the error:
raise session[:user_id].inspect
After confirming the appropriate user_id is in the session cookie using the above method, you could also put the following at the end of your current_user helper method to confirm that a user is actually being returned:
raise #current_user.inspect
What is the logic you are using to create the session[:user_id]? Also, you may want to clear your browser cache or open an Incognito Window (in chrome it is cmd + shift + n) and go back through the sign in process of your app.

Refactoring editing user in Rails

Im working with a medium sized Rails application and I do this in every controller:
def create
#object = Model.new(params[:model].merge(editing_user: current_user))
...
end
def update
#object = Model.find(params[:id])
#object.editing_user = current_user
...
end
Setting the editing user over and over again is not DRY. I thought about cleaning this up with an observer but it would need access to the current user. Observers do not have access to the current user, neither should they (Law of Demeter).
Any suggestions how to DRY this up between controllers?
class ApplicationController < ActionController::Base
before_filter :init_request
def init_request
params[:editing_user] = current_user
end
end
I like using decent_exposure to dry up my controllers. It automatically finds or initializes a model instance, based on whether an :id was passed as a param, and it assigns the attributes from params[:model].
To finish drying up your code, you could use the new strategy support (see the end of the readme) to automatically set the editing_user attribute on your model.
You could try an after_filter for this. Perhaps something like so:
class ApplicationController < ActionController::Base
after_filter :set_editing_user
def set_editing_user
#object.update_attribute(:editing_user, current_user) if #object && current_user
end
The difficulty, of course, is that you'll be saving the object twice per call. Generally though creations and updates don't happen so frequently that two database commits is a serious problem, but if you expect to be the next Twitter -- with massive database insertion load -- it could be an issue.
You could also possibly set this in a before_filter, but then you'd have to find or set the object in a previous before_filter. Otherwise #object will always be nil and the before_filter will never fire. You can use the filter ordering methods prepend_before_filter and append_before_filter to ensure the correct sequencing of these filters.

Resources