Situation: rails 3.2 app with a demo period, after which users must start paying for the service.
Question: If a user does not add a payment method, or does not choose a payment plan, what is the recommended way of restricting user access to the 'paid' part of the web app?
I need something that sorts users as follows:
if user.admin? || user.in_demo || user.has_all_payment_data
# carry on
elsif user.should_add_payment_method
# send them to add payment method page
elsif user.should_choose_plan
# send them to add plan
else
# redirect to home page or whatever
end
I've started off with a before_filter on the application controller that checks the payment status of the user on every request and redirects them accordingly (skipping this in places like the homepage/profile editing etc.), but I'm thinking there must be a better way, as it's rapidly getting too complicated and it just feels wrong having all that complexity in the application controller. I've been looking at user roles libraries like cancan but I can't find anything that fits.
There is a post by Jonas Nicklas (creator of Capybara and CarrierWave) in which he explains in some detail how to take a simpler approach than CanCan's. His approach is based on an additional plain Ruby class for each model you want to create authorization rules for.
Simple authorization in Ruby on Rails apps (Elabs blog)
They have offloaded that solution into a gem named Pundit, but it really seems simple enough to be able to implement from scratch.
Pundit gem (GitHub)
I would suggest a before_filter in the application controller, then using skip_filter in individual controllers to bypass it for actions that non-paid users can access, e.g:
class ApplicationController < ActionController::Base
before_filter :check_payment
...
end
class UserController < ApplicationController
skip_filter :check_payment, :only => [:login, :logout, ...]
...
end
This keeps the access contained to the relevant controllers, rather than needing an increasingly large :except => ... on the filter itself.
Related
I am building a daily deal app on Rails to train myself to Ruby on Rails.
I have installed authentication with devise/cancan/rolify.
I'd like to create in cancan two type of users
users who confirmed
users who did not confirmed yet
How can I achieve that ? how can I access on devise users who have and those who have not confirmed their account(i.e clicked on the activation link sent to them by email).
There is no need to add roles for confirmed and unconfirmed. You can use user.confirmed? in your ability.rb file to control authorization:
# models/ability.rb
if user.confirmed?
can :manage, Model
end
if !user.confirmed?
can :view, Model
end
Note: you can use an if/else construct, but I prefer to keep my rules nicely separated.
In regards to your comments, you're reimplementing what's already been done. With cancan you can use load_and_authorize_resource (see: here).
class ProductsController < ActionController::Base
load_and_authorize_resource
end
That's it. The user will receive an "unauthorized" response if they try to access without the required permissions.
I highly recommend you read through the documentation for rolify and cancan.
I am about to implement authorization for my Rails app.
I know I can use Cancan, but in my case all the roles I have are: user and admin. I see that people always secure the models with roles (like cancan does).
My take on authorization would be to just add:
before_filter :redirect_if_not_admin :only => :create, ...
and then:
def redirect_if_not_admin
if !#user.is_admin?
redirect_to :root
end
in all my controllers and the actions I want to restrict from being accessed.
Q1. Is this in any way less secure than restricting the access to the models' attributes?
Q2. Is there a way for someone to bypass the before_filter and the redirect and access my controller code?
thanks in advance
Sure -- just add the before_filter and your redirect handler method in application_controller.rb. As long as your user model has a method "is_admin?" this will work fine.
It is as secure as any other method (in fact, it is the method used in most cases like this). The only way to bypass the before filter would be if your code allowed it. Indeed, there are sometimes cases where you want a specific controller method to be allowed, in which case you can override the application_controller.rb filters in specific controllers using the :except or :only conditions.
My rails app is pretty much a front-end to several web services. I persist my User model, and that's about it. I need to add authorization to my web app, using Devise for authentication. I've noticed CanCan and acl9 seem to work mostly on instances of ActiveRecord models. Would CanCan or acl9 still fit my needs? Any tips on using either of these libraries in my situation?
Should I look for something that works more on actions instead of instances?
Also, these are both Role based systems, and I'm thinking of using a permission based system. Would they still be a good fit?
I can't speak for acl9. However, the cancan wiki does claim that "It is easy to make your own [model] adapter if one is not provided." https://github.com/ryanb/cancan/wiki/Model-Adapter In other words, even though you're not using ActiveRecord, you might still be able to use cancan.
Then again, if you're not planning on having roles, your ability definitions in cancan might be a little redundant looking, eg.:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :create, Widget if user.has_permission(:create_widgets)
can :delete, Widget if user.has_permission(:delete_widgets)
can :herp, Derp if user.has_permission(:herp_derp)
end
end
It would be great if you could use cancan just for its controller action authorization methods, but I don't know if that's possible. Good luck.
Just to (finally :) answer for acl9.
Acl9 is composed of two very separate pieces, the Access Control Subsystem which is all the authorizing stuff you put in your controller, and the Role Subsystem which is setting/checking/removing roles from an authenticated user.
The only thing that the Access Control Subsystem ever calls is current_user.has_role?( role, obj=nil). So, the Role Subsystem has zero dependency on ActiveRecord, associations, database, etc. There is a helper (acts_as_authorization_subject) which adds an ActiveRecord-dependent has_role? method to a class, but that's entirely optional and you're free to implement your own has_role? method (which can also fallback to calling super to get the acl9 one) and implement your access checks however you please. So, you said that you do persist your user model, but let's say you want a role for your user to be the admin of a school, but that school is a web service call into some remote system.
## in your model
class User < ActiveRecord::Base
def has_role? role, obj=nil
role == :admin &&
obj == school &&
school[:admin] == id # <-- just making up how we know we're the admin of the remote school
end
end
def school
#school ||= School.get_by_user_id(id)
end
end
## in your controller
class SomeController < ApplicationController
before_action :set_school
access_control do
allow :admin, of: :school
end
private
def set_school
#school = School.get_by_id(params[:school_id])
end
end
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
I use rspec, devise and cancan at the moment. To be honest, I find cancan to be very confusing and I am encountering a lot difficulties in picking it up and using it effectively. The docs are not very in depth making this extremely difficult for me to debug (check my past questions).
Is there an alternative to CanCan that's also easily integratable in the other tools I am using?
As of early 2014, Pundit is a popular alternative to CanCan (and the successor CanCanCan). Pundit matches each controller with a policy object. Unlike CanCan, there's no central rule file for all access control. It's simpler than CanCan. There's an example app from the RailsApps project:
Rails Pundit Example
I've also written a tutorial on Rails Authorization with Pundit.
Another alternative is the Authority gem from Nathan Long. All your rules logic goes in Ruby classes called "authorizers" that are associated with models.
Some guys are continuing the development of cancan. Check it out:
https://github.com/CanCanCommunity/cancancan
For the same reason i've done this: http://mcasimir.github.com/checkin/.
Checkin is an authorization gem that is independent from the role/authentication library you use.
You can express even complex rules rather simply with a declarative/cascading permissions DSL.
I found it very handy. The debug is also supported via the explain method that will log the authorization process on every request.
Here are some of the features:
Handy DSL to define roles and permissions with a declarative approach
Check authorization for CRUD operations automatically
Standard way to rescue from Authorization errors
Authorization subject decoupled from model (compatible with any autentication system)
Role-based authorization decoupled from role system (compatible with any role
system)
Decorator for current_user and other subject objects
Scoped authorization rules
Cascading authorization rules
Simple: even complex authorization behaviour is understandable at glimpse and
easily predictable
Support for controller based mass assignment protection
Here is a very simple example of the DSL:
class UserSubject < Checkin::Subject
role :guest, :alias => :anonymous do
!subject_model
end
role :logged_in, :alias => [:connected] do
!!subject_model
end
role :owner, :require => [:logged_in], :method => :own do |object|
object && ( object.respond_to?(:author) && ( subject_model == object.author ) ) || ( object.respond_to?(:owner) && ( subject_model == object.owner ) )
end
role :administrator, :require => :logged_in, :alias => :admin do
subject_model.has_role?(:administrator)
end
#
# Permissions
#
permissions :for => :comments do
allow :administrators
allow :logged_in, :to => [:create]
deny
end
# Admin
scope :admin do
permissions do
allow :administrators
allow :owners, :to => [:edit, :update]
deny
end
end
end
Checking roles and permissions:
subject = UserSubject.new(User.first, :scope => :admin)
subject.logged_in?
subject.guest?
subject.own?(Post.first)
subject.can_edit?(Post.first)
I apologize for being so wordy.
I found https://github.com/stffn/declarative_authorization to be rather complete. And it's logical to use.
Sounds like Pundit may be coming up strong. It has a less 'magic', more straightforward approach.
https://github.com/elabs/pundit
I am currently exploring Heimdallr. The one feature it has that most of these cancan alternatives don't are restricted scopes for index actions.
You also might want to check out this "ultra lite authorization library" - six
Check TheRole gem. very interesting alternative for cancan
I'd recommend Action Access, it's much simpler and straightforward, has seamless integration with Rails and it's very lightweight. It boils down to this:
class ArticlesController < ApplicationController
let :admin, :all
let :user, [:index, :show]
# ...
end
This will automatically lock the controller, allowing admins to access every action, users only to show or index articles and anyone else will be rejected and redirected with an alert.
If you need more control, you can use not_authorized! inside actions to check and reject access.
This makes controllers to be self contained, everything related to the controller is within the controller. This also makes it very modular and avoids leaving forgotten trash anywhere else when you refactor.
It's completely independent of the authentication system and it can work without User models or predefined roles. All you need is to set the clearance level for the current request:
class ApplicationController < ActionController::Base
def current_clearance_level
session[:role] || :guest
end
end
You may return whatever your app requires, like current_user.role for example.
It also bundles a set of handy model additions that allow to extend user models and do things like:
<% if current_user.can? :edit, :article %>
<%= link_to 'Edit article', edit_article_path(#article) %>
<% end %>
Here :article refers to ArticlesController, so the link will only be displayed if the current user is authorized to access the edit action in ArticlesController. It supports namespaces too.
It allows to lock controllers by default, customize the redirection path and the alert message, etc. Checkout the documentation for more.
There's also Consul.
You create a Power class with methods that return only those objects the user has access to, instead of loading objects and check permissions on them. Talk by the author.
I have installed and implemented the plugin restful_authentication from technoweenie.
My application is, for the most part, intended to be open, except for the case of say, writing a review.
So I have a reviews_controller.rb, but the only time I want to care whether the user is logged in or not is when they are submitting a specific action add_review.
add_review is an action on the vendor_controller.rb because the form is displayed on vendors/show. Then it redirects back to vendor/show to show the update (thinking of changing this to ajax).
If the person is NOT logged in, I want it to redirect to the login/signup page.
The instructions for restful_authentication show applying the include AuthenticatedSystem at the controller level, but I don't want to be authenticating with other actions in that controller.
How do I do this?
Include the AuthenticatedSystem module in the controller you need it for (or ApplicationController if you need it for more than one), and then use the before_filter applied only to the actions you want, e.g.:
class VendorsController < ApplicationController
include AuthenticatedSystem
before_filter :login_required, :only => [:add_review]
end
I implore you to use the restful create action rather than your own add_review action. Read more about restful routing at: http://guides.rubyonrails.org/routing.html