The question is generalized but I want to ask about a specific case which I want to solve.
I'm working with a really really smelly code base of e-commerce app and I want to refactor it. I thought I should start with the User authentication.
Problem
Before every action in any controller, we check if the user is of a particular type: Guest, Signed-In or Admin and also, if the user is allowed to access this action based on the type. If all the conditions are met, then the action is executed. And this happens in majority of the actions in majority of the controllers.
My thinking
I know this code is smelly because checking if the user is of a particular type and (s)he has access to an action is not the action's responsibility.
My solution which may or may not be possible
We can make a SessionsController (or some other name) and let it handle the authentication and authorization part. But I want the SessionsController to do its job automatically before every request. i.e. Every request should go through the SessionsController and then this controller will decide whether or not to forward the request to the appropriate controller.
I search Google for this but didn't find anything. So my logical conclusion is that passing a request through a series of controllers might not be possible. But I'm not sure. So if it is possible, guide me how to do it. And if it is not possible, then suggest any other way to do it.
This sounds like a perfect example in which one or multiple before_action can be used. You can place a before_action in your ApplicationController:
# in app/controllers/application_controller.rb
private
def authorize_admin
render status: 401 unless current_user? && current_user.admin?
end
Then you can declare in any controller in which you want to run this method before running any action.
# in any controller - even the ApplicationController
before_action :authenticate
You can configure before_action to only run on certain conditions or with certain actions. Just a have a look at the how to use Filters in the Rails Guides.
Related
I am using Devise for authentication, and I have "modules" in my rails app. I am trying to figure out the best approach to security. For example, here are the few things that I want to accomplish:
I want the application controller to require login, unless they are accessing the registrations controller, in which case they are just submitting registration details.
On the application level, I would like to define scopes, permitting access to certain controllers that the user's company has access to.
Additionally, on each request, I want to verify that any IDs in the URL (whether it's a GET, POST, whatever), the user's company has access to that controller and ID in the parameter. (So they can't access Report ID 9 if their company doesn't have a report ID 9 associated with it)
I feel like this may be scalable, but I've never done this before so I'm not quite sure.
Bullet 1
In the ApplicationController, I would like to do something like this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user! unless controller == "Registrations"
end
I tried placing a binding.pry in the application controller, but the controller_path seems to always be application. Not sure if there's an easy way to accomplish this without going to each individual controller, which I'm hoping to avoid because I don't want to accidentally forget something when I add a new controller down the road (basically going against DRY).
Maybe I can implement a security controller and have every controller inherit from it? Never did this before but this might work if I can't accomplish what I'm trying to do in the Application Controller.
Bullet 2
I have tried to access Devise's current_user variable from the Application Controller, but it does not exist, so I'm not sure if I can check the user's permissions from the ApplicationController. Again, I'd love to avoid having to place this check in each controller because as the application expands, I may eventually forget to implement checks.
EDIT
So it looks like I have bullet 1 addressed, but now I'm trying to figure out the other 2. Being able to implement some type of "scope" or permission module at the application level. Any ideas?
The skip_before_action directive can suppress execution of a before_action filter that's already defined. You can use this to turn off an action for a controller:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
class RegistrationsController < ApplicationController
skip_before_action :authenticate_user!
end
The thing to note here is Ruby is a highly dynamic programming language and code can be executed while the class is being defined. It's important to pay close attention to when certain statements are run, as things like unless, the statement, tend to run immediately when in that context.
You'll see some others that allow deferred execution like in ActiveRecord with:
validates :name, unless: :anonymous?
Where that validates method has a specific option called unless which is distinct from the keyword unless. That defines a validation trigger with a condition attached to it.
On the other hand this code, while visually similar, is completely different:
validates :name unless anonymous?
This depends on a method called anonymous? being available at the class level and if it returns a non nil or false value will execute the validates function.
I would like to show the users which fields have been modified following his PUT/PATCH request
For example, I have a big "project" form, with several fields, but my user decided to only update the deadline and the project name. After he clicks the "save" button, I would like to show some message saying "You have successfully updated : name, deadline"
If it's possible, I would like some generic code that would detect the update action and infer the variable name. By generic I mean, I want to implement this in my ApplicationController, so I don't have to add code in every controller#update action
Let's look at this sample code from controllers/entreprise_controller.rb
def update
if #entreprise.update_attributes(entreprise_params)
redirect_to #entreprise, notice: "Entreprise éditée"
else
render 'edit'
end
end
Here's an idea of steps to reach my goal. Could you help me with each of these ? Or suggest a better approach ?
Detect that we are doing a CRUD update action, for example from the action name in the code, that should always be update (how can I read, from the code, the name of the action being executed ?)
Guess the variable name : here #entreprise, it can be inferred from the file for example (or maybe calling self.class and doing some regex ?)
Save the list of variables that are going to be updated (maybe some tricks involving before_action and after_action and dirty_tracking ? See my edit.)
Provide this list as a GET argument for the redirect to #entreprise (should be pretty straightforward)
Show this list to the user (this part is OK for me)
EDIT concerning Dirty Tracking
Mongoid already implements this. However the main problem is getting the intermediate variable before it is saved. Each controller instanciates the variable like #entreprise during a before_action callback. And if I add a before_action in my ApplicationController, it will fire before, so no variable is available yet. And as regards a possible after_action in ApplicationController, the doc says "Any persistence operation clears the changes." so it's dead already. I probably cannot get away without rewriting every controller ?
Dirty Tracking & controller in a nutshell :
prepend_before_action
before_action of ApplicationController
before_action of EntrepriseController (which includes set_entreprise, where the variable #entreprise is defined so as to proceed with update)
If we can get a callback to HERE, it would let us inspect the object for dirty tracking information, as the object exist in memory, we can use #entreprise.attributes=entreprise_params and look at the dirty info (where entreprise_params is the strong parameters for #entreprise)
action : on success, it will store the info in the DB, and we lose dirty tracking info
after_action
I'm wanting to create an admin backend for a practice app. After reading around I've come to a general idea of what to do but would like some clarification:
Use namespace to route the backend at example.com/admin...
Put any adminifiable resources inside namespace e.g resources :posts
At this point do I duplicate the normal (public facing) controllers and put them into the admin directory? (Along with CRUD views)
In theory the public facing controllers only need index and show actions, right? As new/create/update/destroy will only be accessed in the admin/controller.
Any clarification or advice is greatly appreciated. Just trying to wrap my head around this.
I would recommend against duplicating your controllers, or any part of your application for that matter. That goes entirely against the DRY principle, which stands for "Don't Repeat Yourself." Duplicate code becomes really hard to maintain and test as your application grows.
Instead, I would recommend limiting access to certain actions using before filters. For example, let's say that you want users to be able to create posts, read posts and see listings of posts. In your PostsController, you can have something like this:
before_action :admin_user?, only: [:edit, :destroy]
Note: before_action is just the new name for before_filters.
So then actions like index would execute normally for all users, but if a user calls the destroy action, the controller would first check to see if the user is an admin, by calling an admin_user? method (typically defined in ApplicationController). This method could be a simple conditional, like "if the user is not an admin, flash an error message and redirect them back to where they were before the request" and then use it to protect any action or resource you want. You could also use it in the views to show delete buttons on posts only if the user is an admin, for instance.
That's for resource-specific actions. Often times it's also a good idea to have a section of the site that consolidates resource views and administrative actions. This would be its own controller/view (I call mine AdminController) and you can protect all actions in it with the above method:
before_action :admin_user?
To make your resources available to AdminController using the methods defined inside the individual resource controllers, you can do this in routes.rb:
namespace :admin do
resources :users
end
This will make it so that http://yoursite.com/admin/users/index will still call the index action in the Users controller, but it will happen within the context of an admin user (because of the before_action above).
I have added geocode_ip_address in ApplicationController so I can get the user location info from their session.
I have some controllers that I don't want them to be checked against Geokit. It really slows application and there's no need for geo check there.
Since its called like geocode_ip_address and not as a before_filter I'm not able to use skip_before_filter
Any idea?
it actually uses store_ip_location filter so you can put
skip_before_filter :store_ip_location
That being said it stores the result of the geo code inside a cookie that is being checked before making a service call so the subsequent calls should not impact performance that much
You can selectively NOT INVOKE the code based on the controller you're in like so:
unless ["controller1", "controller2", "etc"].member?(params[:controller])
geocode_ip_address
end
Put the names of the controllers you don't want the code to run for in the list and it won't be invoked in those controllers.
You could also create a constant that's a list of controllers like this:
CONTROLLERS_TO_NOT_FILTER_BY_GEOCODE = ["controller1", "controller2", "etc"]
unless CONTROLLERS_TO_NOT_FILTER_BY_GEOCODE.member?(params[:controller])
geocode_ip_address
end
I've put all of my user-authentication code in one place, namely lib/auth.rb. It looks like this:
lib/auth.rb
module Admin
def do_i_have_permission_to?(permission)
# Code to check all of this goes here
end
end
I include this module as part of the application helper, so these functions are available in all the views:
application_helper.rb
require 'auth'
module ApplicationHelper
include Admin
# other stuff here
end
And I also include it as part of the application controller, so the controllers likewise can call the functions:
application.rb
require 'auth'
class ApplicationController < ActionController::Base
include Admin
end
So far, so good.
The problem is that my application is not like a normal web app. Specifically, more than one user can be logged into the system from the same computer at the same time (using the same browser). I do authentication for actions by looking at all the people who are logged in from that IP and if they can all do it, it passes.
What this means is that, if an admin wants to do something, that admin has to log everyone else out first, which is annoying. But we want the admin seal of approval on everything the admin does. So the suggestion given to me was to have it so the admin can supply a username/password combo on any page they would not normally have access to (e.g. an 'edit user' page would have these extra input fields) and the authentication routines would check for that. This means
Admin::do_i_have_permission_to?(permission)
needs to get at the current request parameters. I can't just use params[:foo] like I would in a controller, because params isn't defined; similarly request.parameters[:foo] will also not work. My searching has revealed:
The current search parameters are in the current request,
The current request is in the current controller,
The current controller is in the current dispatcher, and
I'm not sure the current dispatcher is kept anywhere.
That said, experience tells me that when I'm jumping through this many hoops, I'm very probably Doing It Wrong. So what is the right way to do it? Options I've considered are:
Just move all the functions currently in auth.rb into the ApplicationHelper where (I think) they'll have access to the request and such. Works, but clutters the hell out of the helper.
Move all the functions somewhere else they'll see those methods (I don't know where)
I'm just plain missing something.
In a typical Rails application, authentication information is stored in the active session, not the parameters. As such, it's pretty straightforward to write a helper that does what you want.
It seems rather unorthodox to create a module that is then included in ApplicationHelper. The traditional approach is to create a separate helper which in this case would probably be called AuthenticationHelper. This can then be included in any required controllers, or if you prefer, loaded into ApplicationController to make it available universally.
In general terms, Helpers should not include other Helpers. It is better to simply load multiple helpers into a given Controller.
Helper methods have full access to any instance variables declared within the controller context they are operating from. To be specific, these are instance variables only (#name) and not local variables (name). Helper methods are executed for a particular view as well.
Further, I'm not sure why a user would be providing credentials and performing an operation in the same step, at least for traditional web-based apps. Usually the process is to log in and then perform an action separately.
However, in the case of an API where each transaction is an independent operation, the most straightforward approach is to do is pull out the relevant request parameters that deal with authentication, establish some controller instance variables, and then proceed to perform the particular request given the constraints that the credentials impose.
The approach I usually follow for this sort of thing is to layer in an authentication structure in the ApplicationController itself which can perform the required checks. These are protected methods.
While it's tempting to roll in a whole heap of them such as can_edit_user? and can_create_group? these very quickly get out of hand. It is a simpler design to put in a hook for a general-purpose can_perform? or has_authority_to? method that is passed an operation and any required parameters.
For example, a very rough implementation:
class ApplicationController < ActionController::Base
protected
def has_authority_to?(operation, conditions = { })
AuthenticationCheck.send(operation, conditions)
rescue
false
end
end
module AuthenticationCheck
def self.edit_user?(conditions)
session_user == conditions[:user]
end
end
class UserController
# ...
def edit
#user = User.find(params[:id])
unless (has_authority_to?(:edit_user, :user => #user))
render(:partial => 'common/access_denied', :status => :forbidden)
end
rescue ActiveRecord::RecordNotFound
render(:partial => 'users/not_found')
end
end
Obviously you'd want to roll a lot of the authority checks into before_filter blocks to avoid repetition and to promote consistency.
A full framework example might be of more help, such as the Wristband user authentication system:
http://github.com/theworkinggroup/wristband/tree/master