How can I limit access to UI features? - ruby-on-rails

I have used declarative authorization gems like cancan and cancancan in the past to control access to data. However, in this new application I am trying to control access to actual features.
It's your typical SaaS model, where progressively more features are available depending upon the level at which the user has subscribed.
Most of the features are available by clicking different icons or menu items throughout the application.
I'm writing to inquire whether there is a well-known implementation pattern for this that I'm not aware of before I roll my own.
Here are the different types of things that will be limited based upon the subscription level:
Features accessible by icon
Features accessible by menu item
Certain Reports (each of which has a ReportDefinition defining it.)
Certain Uploads (each of which has a FileType defining it.)
Certain BackgroundProcesses (each of which has a ProcessType defining it.)
Each Subscription has a Plan. It's simple enough to tie that Plan into items 3, 4, and 5 above and use cancancan for accessibility by current_user. However, features 1 and 2 are a different story. I can see wrapping their accessibility in a view helper which checks a Feature/Plan list. But that only handles the view. If the user knows the URL, they'd still be able to access the feature by typing the URL in directly. In that case, do I need to handle the authorization again at the controller action level, or is there instead some kind of middleware I could put in to limit accessibility to the features?
Thanks very much.

If it's a simple app, I simply add an admin column on User. If there's more than 2 user types (admin/non-admin/author/editor/etc) then I would make it an Enum field instead of boolean.
Then, inside user.rb I add several methods...
def is_admin?
admin?
end
def is_author?
!admin?
end
From there, I also add some code in application_controller.rb which raise an exception:
def unauthorized
head(:unauthorized)
end
def unprocessable
head(:unprocessable_entity)
end
I also add a current_user method in application_controller.rb:
helper_method :current_user
def current_user
#user ||= User.find(session[:user_id])
end
Then in my views, that's where I handle "hiding" things or disabling buttons.
<%= if current_user.is_admin? %>
<button>Admin button</button>
<% else %>
<button>Author button</button>
<% end %>
This of course is NOT security (view-layer only changes), which is why I also return early from controllers using the previous methods I laid out:
def destroy
return unauthorized unless current_user.is_admin?
# ... delete code here
end
For the example above dont forget to use return or the code will keep executing.
For things that are more than simple, I use Pundit.

I would simple roll my own using enum to set different access or subscription levels. Then just write a before_action called can_access? to block off entire actions. Then I would set some conditionals or view_helpers in the view to block access to certain UI elements.

https://github.com/jnunemaker/flipper is a good solution and does exactly what you are looking for.
Otherwise, like you said, cancancan is good for a naive solution.

Related

Determining if an association between two models has been created within one model?

I've been researching this topic for a day now, and I haven't seen a solution that could adequately allow this. I would have even give up and said that it's not possible, but I see large companies achieving this in their apps!
I need to know if the current user is following another user. I need to know this many times (for the current_user) without polling the DB again
The solution needs to be friendly for reuse. A solution (that's not friendly for reuse) I had come up with is as follows:
module UsersHelper
def is_following?(user)
return false if current_user == user
user.is_following = Relationship.find_by(followed_id: user.id, follower_id: current_user.id)
end
end
is_following?(#user) can now be called in any controller
Notice that I'm able to access current_user because this helper method will be called in a controller, not a model.
This implementation is cool for one model, maybe two... except I need to do this in almost every many-many relationship I have in the app. So it has to be scalable.
I'm referencing exactly what Twitter does with their following.

Fewer queries on Rails views

In my application I have a header that has many rules, since I have many types of profiles.
I want to make just one query (maybe two) to get the user and make all the checks that I need
In my application controller I have this method:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
To check the current user, but I keep calling it on my header.html.erb, if I check it, like, 10 times, I will do 10 queries...
As far as I know when I first call current_user I would get a variable #current_user to use. But this is clear for me when I have a controller. I call current_user and on my view I just check #current_user.something. since /layouts/_header.html.erb doesn't have a controller, how can I do this ?
What you have should not perform multiple queries if the lookup is successful -- it will set #current_user to the return value of the query and then just return that in the future. However, if no user is found, #current_user will be initialized to nil and so additional calls to current_user will execute the query again.
One possible workaround is to used the defined? operator like so:
def current_user
return #current_user if defined?(#current_user)
#current_user = User.find_by_remember_token(cookies[:remember_token])
end
However I don't love this implementation, because if anything defines #current_user in the current closure, then this will return the value whether the lookup has been done or not. You could also set another variable that tracks whether the current user has been looked up and only execute the query if it has, but that seems ugly to me, too.
For a more robust solution to this general problem, see the memoist gem. Or better yet, have you considered whether devise and/or warden might be right for your authentication needs?
since /layouts/_header.html.erb doesn't have a controller, how can I do this ?
Your layouts do have controllers. It's impossible for them to be rendered otherwise.
What you're doing now will work as it's written, and it will minimize the number of queries, and your layout files will have access to the current_user helper, provided you've made it available to your views via helper_method :current_user in your controller.
There's really nothing wrong with your code, whatsoever; all the problems you think you're having, you're probably not really having.
From the following
" I have a header that has many rules, since I have many types of profiles"
I guess you are verifying multiple conditions based on user role. If my assumption is correct then
Better create separate partials for each role.For example if you have admin role then
create partial in layouts/_admin.html.erb
Then you can include it as
<%= render "layouts/" + current_user.role %>

Rails + Devise: How to restrict a user from editing records that do not belong to him

I'm using Rails, Devise and Mongoid.
A user can only have one project (has_one :profile) but all users (and non authenticated users) can see a list of projects (I have this working). When a user is logged in they see "edit and delete" buttons next to the projects (via wrapping those buttons in <% if user_signed_in? %>). However, a signed in user sees these buttons next to all project and can edit or delete them all.
How do I restrict a logged on user to only be able to edit only his project?
As a bonus, is it possible to show specific content or html around the project that belongs to the signed in user (such as text that says "this is your project" or an additional class around the project's html)?
CanCan is great when you have multiple users being able to modify the same resource, and you need to keep track of who has which permissions. But if it's strictly the case that only the user who owns the project can modify it, CanCan is probably overkill.
Hide the links in the view as suggested by iouri, and then in your controller do something like:
def edit
if current_user != #project.user
# Redirect them to an error page
else
# Render the view
end
end
Better yet, create a method like:
def user_owns_project?
#project.user == current_user
end
Then set up a before filter for edit, update and destroy to redirect to the error page if the user doesn't own the project.
EDIT: Your before filter will also ned to find the project and set #project. CanCan takes care of this for you too with load_and_authorize_resource, but I'd still avoid using it unless you need, or expect to need, fine-grained permissions control.
Devise is to control "authentication", this should not to be your responsibility.
You want to control "authorizations", for that CanCan is better.

Preventing discoverability in a RESTfully routed model

I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.

Getting the current request in rails from a file in lib/

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

Resources