CanCan explanation of load_and_authorize_resource - ruby-on-rails

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.

Related

How can I use Devise methods in my ConnectionAdapter callback?

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.

Before_action causing too many redirects

I'm wondering what would be the best way to handle the following:
I have an authentication method (used as a before_action) as follows that checks if a user_id is in the session when the login page is requested. If the user_id is detected, then it redirects the user to dashboard path.
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
This is leading to a too many redirect errors which I understand. I can see in pry that it's just evaluating that before_action filter every-time the page loads. That's what leads to too many redirects.
My question is what is the best way to handle this type of setup. Is there a way in rails to only evaluate on the first redirect? I thought of using a temp flag to tell if the redirect happened before. That doesn't seem very elegant though. I'm sure there is an easier/better way to manage it.
Thanks for any advice you can provide.
There has to be an exception on your before_action: you don't want to call it on the dash_path. If a user enters there and is validated, it should stay there (as what the redirect would do) and if it is not validated it should just stay there (as with any other url that fails this validation process).
There is no point on checking if it is validated as the result will always be to stay on the same page.
Then in your controller you have to specify that you want an exception on the before_action:
class SomeController < ApplicationController
before_action: :already_validated, except: [:dash_action]
def is_validated_action # the method that causes the redirect
end
def dash_action # action of dash_path url
end
def already_validated
if session[:uid] == user.id
redirect_to dash_path
end
end
end
If you want some validation before the hypothetical dash_action then create a new method for it. Be sure that you don't have circular references or it will be pretty difficult to debug on the long run.
You can just tell Rails to skip the before filter in the controller that handles the dash_path:
# in the controller
skip_before_action :already_validated
Read about Filters in the Rails Guides.

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 %>

Ruby on rails Access a file from a controller

If I am going about this wrong please let me know I can change it. I have a file in config/initializers/payload_signer.rb. I am trying to use this file in the controller that is called device_enrollment_controller.rb.
PayloadSigner.sign(get_profile)
get_profile is a method in the controller that gets the file I need and returns it. PayloadSigner references the other file. When I try to run this (keeping in mind im sure changes will have to be made in payload_signer for it work right) the error I get is uninitialized constant DeviceEnrollmentController::PayloadSigner. This leads me to believe I am referencing the payload_signer.rb file incorrectly. I have tried things like include and load but so far they are not working.
Any help or guidance is appreciated.
Rails Initializers are called before Controllers or Models. So it won't work. Initializers are not intended for this kind of use. Instead I suggest placing your code in a controller before_filter. Either in the ApplicationController or only in those controllers that require it (e.g. DeviceEnrollmentController). Something like this:
class DeviceEnrollmentController # Or ApplicationController
before_filter :sign_payload
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end
EDIT: Another example:
class DeviceEnrollmentController
# The filter is only applied to the sign action
# (that's what the :only parameter does).
before_filter :sign_payload, :only => [:sign]
# Browsing to /show, you render this magic button of yours.
def show
# Render page that holds the button
end
# The magic button is bound to the /sign route.
# Clicking on the button calls this action.
def sign
# When you get here, the #sign_payload method
# has already been called.
end
protected
def get_profile
# Magic
end
def sign_payload
PayloadSigner.sign(get_profile)
end
end

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