RubyRedGrapefruit
RubyRedGrapefruit

Reputation: 12224

How can I limit access to UI features?

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:

  1. Features accessible by icon
  2. Features accessible by menu item
  3. Certain Reports (each of which has a ReportDefinition defining it.)
  4. Certain Uploads (each of which has a FileType defining it.)
  5. 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.

Upvotes: 3

Views: 665

Answers (3)

Tallboy
Tallboy

Reputation: 13467

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.

Upvotes: 2

Verty00
Verty00

Reputation: 743

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.

Upvotes: 1

Josh Brody
Josh Brody

Reputation: 5363

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.

Upvotes: 0

Related Questions