I have an app where there are multiple "Admin-ish" roles. Imagine you have a super-admin that can edit anything, and also a site-admin that can edit any information about his site.
So both admin/sites and siteadmin/sites are basically the exact same view.
What's the right way to set this up (views/controllers)? I'm trying to keep things as DRY as possible.
I'm a huge fan of the Rails Authorization plugin
This allows you to easily assign roles to objects and use blocks to grant access.
#a.has_role('admin')
#b.has_role('super_admin')
permit "admin or super_admin' do
# Show admin and super_admin stuff
end
permit 'super_admin' {}
You can also grant access on other objects or classes.
#user.has_role('photographer', #photo)
#user.has_role('news_poster', NewsPost)
Related
If I set up a basic User model in Rails, and give it an is_admin:boolean, default: false attribute, what's the best way to prevent non-admin users from changing this?
It seems like the sort of logic that should really go into the model, but what's the best way to construct it? ActiveRecord callback functions?
I know I could put this into the controller's #update method, but that doesn't seem to match MVC best practices. (And seems less portable.)
What's the best approach here?
Denying access with a redirect is a job perfect for a controller, so doing it in private before_filter method would be sufficient and justified in my opinion.
Why do you say that the logic should go in the model?
It depends on your implementation. If the logic is dependent on the current_user (i.e. can a current admin set another user to be an admin as well), then the logic should go in the controller. Since you tagged the question as Rails 4, the logic will go in the permitted params in your controller:
def user_params
if current_user.is_admin
params.require(:user).permit(...your attributes here with is_admin...)
else
params.require(:user).permit(...your attributes here without is_admin...)
end
end
I would set admin privileges only in the console like that:
a = User.find_by(email: "user#example.com")
a.toggle!(:admin)
You would prevent somebody cracking into your site to gain admin privileges.
I have 3 types of "users"
Shop
Provider
Customer
The last 2 users will have similar attributes such as
First Name
Last Name
and so on
And the Shop user type will have the most contrast between types.
As far as behaviours they will all be quite different, although Provider and Shop will inherent many of customer behaviours.
It seems the behaviours can be dealt with CanCan as I've researched.
I'm now attempting to how I should authenticate these types.
I have looked at the STI model but I couldn't grasp where I would these extra attributes.
My mental model is as follower:
User is a table and model
The types are abstract models that inherit from this.
So I'm wondering, how do I add attributes such as Business address for just the Shop type?
Or is it that the User Table has a column called Type and that type is associated with these type tables? And within the type tables are the extra attributes?
Don't even bother bringing Devise into this; Devise is for authentication not authorization. Authentication is determining whether or not someone who visits your site is who you think they are, like logging in. Authorization is deciding whether or not a user is allowed to perform some sort of action, like creating a new post.
What you want to do is have some sort of system that assigns a normal user your three different types; CanCan will do something like that. One way to do this on your own is using a permissions number based system. Let's say normal users have permissions level at 100, shop has a level at 50, and provider at 25. Using this system you can determine what actions a user can perform without having to make separate models, which will make your schema unnecessarily complicated. Here's an example of how this would work with say the UserController:
def show
if current_user.permissions == 100
render "customer_show"
elsif current_user.permissions == 50
render "shop_show"
else
render "provider_show"
end
end
The easiest way to do this is to add a column to the user's table called permissions that defaults to say 100 when a new row is created. Here's what that migration would look like:
def change
add_column :users, :permissions, :integer, default: 100
end
As for authenticating, don't worry about it. Let devise do it's thing; every user, no matter what type, will login and sign up in the same way, maybe just having separate forms for each that has a hidden field to set the permissions level for that specific kind of user.
I know I'm late to the party but I'm putting this out for future SO searchers. You CAN authorize actions with Devise. If you have devise models for 'buyer' & 'seller' you can add 'buyer_signed_in?' for whatever action you only want buyers to be able to do. You can also do more specific role-based authorizations as well - check out Devise' page
All in all, Tsiege's solution sounds pretty interesting. Let us know if you had any success with it!
If I've got a simple rails user model that has an array of roles, is it sufficient enough to control access to actions by simply checking the model's role attribute for that role and blocking/proceeding accordingly?
Is there an advanced system that I ought to leverage due to unforeseen complexity?
Important: I'm not looking to authorize users/roles to models (I am already aware of CanCan). I'm looking to do security at the controller level so that I can break out the functionality in finer detail.
Even more important: Seriously, I'm not necessarily asking about CanCan, please read the question carefully and pay attention! :)
Question 1: YES, Question 2: NO.
I just keep this simple
If you check the models attribute in the controller, the controller will restrict all users that do not have this attribute set.
ex:
def create
#user.find(params[:user_id])
if #user.admin?
#post.new(params[:post])
#post.create!
end
end
make a method in the user model
def admin?
role == "Admin"
end
You should make better code than this. To much logic in the controller, but this will keep all, except admins out.
I have a rails app that is multi tenant. It has resources for Account, Client, and Transactions. An Account has many Clients has many Transactions. I want to make sure I can never accidentally do Client.find, Transaction.find, etc (everything must go through the_account.clients.find, or client.transactions.find, etc). I want to do this so I don't accidentally show the wrong users things from the wrong account by forgetting to first select on the Account.
Is there a way to disable the Client.find (Client.find_by_name, Client.find_by_etc) but still allow the_account.clients.find?
I don't know about completely disabling Client.find, but I'd probably put a method into the bottom of each of my Client and Transaction controllers to handle lookups. Here's what the Client code would look like:
def collection
if current_user.admin?
Client.all
else
current_user.account.clients
end
end
Then, everywhere in my controller I'd ordinarily use Client.find, I'd substitute collection instead. Gets you all the right records without risk of over-exposure.
In my online store, users are allowed to change certain properties of their orders (e.g., their billing address), but not others (e.g., the origination ip address). Administrators, on the other hand, are allowed to modify all order properties.
Given, this, how can I use :attr_accessible to properly secure my Order model? Or will I have to use it to mark accessible all attributes that administrators can modify and refrain from using Order.update_attributes(params[:order]) in those controller actions that ordinary users can access?
Generally speaking, attr_accessible is not the tool you're looking for and Rails doesn't come with anything built in that does what you want.
If you want fine-grained control over who can update specific attributes in a model, you could do something like:
class Order < ActiveRecord::Base
def update_attributes_as_user(values, user)
values.each do |attribute, value|
# Update the attribute if the user is allowed to
#order.send("#{attribute}=", value) if user.can_modify?(attribute)
end
save
end
end
Then you can change your Order.update_attributes(params[:order]) to Order.update_attributes_as_user(params[:order], current_user) and assuming you implement the User#can_modify? method to return true in the correct cases, it should work.
I had the same problem and now I'm using this gem http://github.com/dmitry/attr_accessible_block
It's easy and used in some production website.
Yes, you'll have to modify the actions, so permissions are checked inside the actions. Calling Order#update_attributes will not work for the general user.
I can't rember a role-based authorization plugin that would allow something you are looking for. This is because these plugins mixin to the controllers and not the models. They would also need to mixin into ActiveRecord::Base to check for attr_accesible etc.