Reputation: 12224
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:
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
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
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
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