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
Related
I have a Rails app where I want some users to be super users with special privileges, like being able to access menus where they can edit content of the site. I'm not 100% sure that my implementation is secure, or the Rails way. It looks like this:
I have created an environment variable using the Figaro gem where I store the email-address of the original site creator:
ENV["site_manager_email"]
As I understand it, Figaro's application.yml works like Rails secrets.yml. My user model have a site_manager BOOLEAN attribute. The original site manager should be able to add other site managers by setting this property to true. In my user model I have a method to check whether users are site managers or not, it looks like this:
def is_site_manager?
self.email.eql?(ENV["site_manager_email"]) || self.site_manager == true
end
In my views where I have content that only site managers should be able to see I use the above method like this:
- if current_user.is_site_manager?
SUPER SECRET CONTENT THAT ONLY SITE MANAGERS SHOULD SEE
In my controllers I'm also planning to add a before action filter to only give access to some actions.
So my question is, is this a secure way to do it? If not, what should I change?
And, is there a more Rails way to do it? If so, how does it look?
What are you using for authentication? Devise?
For admins you can look into roles-based authorization and CanCan:
https://github.com/ryanb/cancan
There is a RailsCast that will show how to set it up:
http://railscasts.com/episodes/192-authorization-with-cancan
Once you install it and add a .role column to your Users you can create Ability.rb in your app/models that will look like something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :administrator
can :manage, :all
else
can :read, :all
can :create, Product
end
end
end
I have a Rails 3 application that uses Cancan for authorization. I have the following logic in my application: An admin has access to manage all other users, and has can :manage, User set in the ability file. A company owner has access to manage all users under his company, and has can :manage, User, company_id: user.company_id set in the ability file.
In my UserController#index, i need to call one method if the user has access to manage all the other users, and another method if the user only can access users from his company. Is there any way to do this with CanCan?
As discussed in the question comments, this sounds like a case of roles vs abilities.
If there already is a role system in place, and the logic for choosing between methods maps directly to these roles, then going through CanCan abilities is unnecessarily complex. CanCan is good at checking for abilities on specific model objects, classes and collections but not at going back to the original logic behind why those abilities were awarded in the first place.
In this specific case, there would need to be a way to refer to the case "can manage all Users in Company X but NOT all Users". It might be possible to accomplish with some if-else structure, but I don't think it is what you actually want. Especially if your ability logic changes over time it may not make sense anymore. One example is the corner case where all users belong to the same company, would it be desirable that the "all Users" method is called even for non-admin company owners?
My suggestion therefore is to check the roles directly, much like you already do in your Abilities class. But I feel your pain. ;)
i'm using cancan with device, in my projet i used probably the same you want
if user_signed_in?
if current_user.has_role? :admin
#users = User.all
end
else
#users = User.where(current.user.company_id == company_id)
end
In controller action you should do something like:
#load #user variable
begin
authorize! :manage, #user
#code when access is granted
rescue CanCan::AccessDenied
#code when access is denied
end
I know this an old question but I also ran into this situation. I was also looking to do all role checking in ability.rb file in one place.
What I ended up doing was far from ideal, but just noting it down if anyone wants to go that road. I defined a new ability like this in the ability.rb:
can :manage, :all_users if user.admin?
Please note that :all_users is just a random name that I chose and not some magic method in cancan.
After defining this ability, I was able to do like this in the controller:
if can? :manage, :all_users
call_method_which_can_access_all_users
else
call_method_which_can_access_only_some_users
end
But, it would have been great if Cancan gave us something like can? :manage, User, :all depending on whether there is a hash for filtering users after the User parameter.
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.
This seems like an easy question that I just can't wrap my head around.
Using Devise for authentication and CanCan for authorization on a new Rails 3 app.
How can I access methods defined in ApplicationController within the Ability class that CanCan provides?
a.k.a., something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # Guest user.
can :create, Post if user_signed_in?
end
end
where user_signed_in? is defined in ApplicationController.
This might not be the answer you wanted, but it seems like you are wanting to mix code concerns that shouldn't be mixed.
Is it a good idea to access user_signed_in? inside your authorisation rules? ... Since authorisation is only concerned with what someone can do, and should not be concerned with if that someone is authenticated (or not).
A before filter (before_filter :authenticate_user!) on your Posts controller to check that your user is authenticated should be enough to do achieve your objective; Your authorisation rules can be run alongside the authentication check, rather than mixed up with it's code.
It's a layered approach :-)
I will need to provide dynamic role assignments (Roles/ Privileges) .More clearly, an end user should be able to create a role, assign permissions to a new user. So I was thinking of storing roles and privileges in a table for each user.
Is there a smart way to do this (any other plugin?),or or should I write code to do this with Declarative Authorization . Some light would help.Thanks!
Try answering these to get closer to a solution:
Are the roles themselves dynamic? i.e. Can the privileges assigned various to roles can be changed through the web interface by an Admin? If yes, then you should be storing this information into your database. For a system like a blog, where roles are pre-defined eg. Admin, Guest and Moderator, Declarative Authorization works like a charm.
How strong is the coupling of permissions to the UI? (Sometimes it just a couple of places you need to restrict, in other cases, like a social network, permissions are a lot more complex and coupled tightly with the UI). If its very tightly coupled, i.e. one action is available to all sorts of roles but the actions these roles perform are limited by their definition, then Declarative Authorization (or the likes) won't help much, you need a legacy system.
I've used CanCan recently in a project and think it was pretty cool. You create an Ability class and use it to decide if the user 'can' perform the action... You could check for existence of permissions in a table in the method, or if their ruleset permits the action.
I took all of this sample code from the github readme:
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Then in your views and your controller you can check authorization levels
<% if can? :update, #article %>
<%= link_to "Edit", edit_article_path(#article) %>
<% end %>
def show
#article = Article.find(params[:id])
authorize! :read, #article
end
Cancan is great for simple/starting projects but you should definitely wrap it if you have a monolithic app. Cancan should be a early solution but not a final one. If your looking at policy objects (pundit) it might be a code smell you need to build your own authorization model. Authorization like integration varies client to client and if your looking for more dynamic solutions or you have too many roles to speak of, cancan is not for you. You may need a more data-driven security model. For example if you can grant someone else access to an entity.