Rails 4 - secure way to give some users admin privileges? - ruby-on-rails

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

Related

Admin can't create items for self with CanCan?

I'm using CanCanCan for authorization. An admin can manage all, so they don't have per-user-id rules. The result is that they can't create items for self by default. It looks like I need to add a bunch of extra plumbing to make create in my controllers work the same for admins as it does for general users. The reason seems to be Ability#attributes_for doesn't provide the admin user with the user_id attribute.
How are other people getting around this? Are you writing code to specifically handle the admin use case in your view or controller?
Relevant parts of the Ability class
if user.admin?
can manage, :all
else
can manage, Purchase, user_id: user.id
end
Example interaction
2.6.2 :012 > Ability.new(User.find(3)).attributes_for(:create, Purchase)
=> {:user_id=>3}
2.6.2 :013 > Ability.new(User.find(4)).attributes_for(:create, Purchase)
=> {}
User 3 is general_user, User 4 is an admin
In the controller
# relying on load_and_authorize_resource
def create
puts #purchase.user_id # => nil for admin, 3 for general user
# have to add this for admin use case
#purchase.user = current_user
...
end
I wouldn't rely on the Ability object to assign user_ids to your new objects. I think it's better to explicitly write it in the controller. It seems more clear what's happening. So basically just initiate a new object in the create method and don't rely on load_and_auhtorize_resource
def create
#purchase = current_user.purchases.new(purchase_attributes)
end
If the user wasn't authorized, CanCanCan would already have interfered.
ps. I have been a CanCanCan user for years, but recently moved to Pundit. I think the way it was designed is way better and clearer than CanCanCan. Check it out if you have the time!
There's a bug in CanCanCan. Defining the ability can(:manage) needs an id. Creating a record doesn't. So the ability file doesn't allow you to create bc it's looking only for record that it can find with an id. if you define can :create, Purchase above where you define the :manage you should be good :D
if user.admin?
can :manage, :all
else
can :create, Purchase
can :manage, Purchase, user_id: user.id
end

Defining Admins Rails

How can I define a method between Users and Admins? I am using Omniauth Twitter for my users but there are actions that I want to make sure users can not do if they are not admins. I have bundled the gem Active admin but because I am not using devise, any user can currently access the Admin dashboard.
If it were me, I'd create a role attribute on your User model, and have it default to say "customer" or whatever a default user is, but have the ability to set some user's role to "admin". You should be able to set that using Active Admin's edit page. Than create a custom method on your User model, or maybe create a helper method, whatever you prefer...something like:
def admin?
if self.role == "admin"
true
else
false
end
That way you wrap sensitive info (like info on your dashboard) in a user.admin? if statement.
if user.admin?
..display stuff..
end
Or check out this: http://activeadmin.info/docs/13-authorization-adapter.html

How can I know if a user has access to all or just some resources with Cancan?

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.

Using CanCan with a static roles list

I’m looking to set up simple role based access on my rails app without using a database, we use CAS to handle the authentication so can capture the username from the CAS variables.
My thought was to map usernames to specific roles in a hash (in application.rb, see below for an idea)) and then use that role to determine if the content is viewable.
ROLES = {
'admin' => [ 'username1', 'username2'],
'standard_user' => ['username3']
}
I’ve been advised to look at the CanCan gem but that seems to want to get the role information from a user model which I don’t want to setup, mainly because we intend to pass over role information as another CAS variable in the near future.
So I wondered if anyone has any idea if this plan is do-able and maybe point me in the direction of any code snippets that might help me
Even with this kind of setup, you should be able to use cancan. If you have a user model where you store the username and the ability is revolving around this model, try the following on ability.rb
# user.rb
ROLES = {
admin: %w[username1 username2],
standard_user: %w[username3]
}
# ability.rb
user ||= User.new # username is blank
if User::ROLES[:admin].include?(user.username)
can :manage, :all
elsif User::ROLES[:standard_user].include?(user.username)
cannot :manage, Invoices
else
...
end

Rails Authorization on Web Services

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

Resources