How to you only allow admins to set admin privileges? - ruby-on-rails

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.

Related

How can I allow only a specific user role to update an attribute to a specific value

Afternoon, got a bit of an issue I am not sure how to resolve.
I am trying to setup some rules that allows only certain types of user roles to update the status attribute on a model to a certain status.
So I looked into doing this with pundit as it seems to be an authorisation issue, however one problem with that is you cannot pass the params to the pundit policy which I would need access too (so I can see what attribute they are trying to change to), and it seems that its bad practise to pass params to a pundit policy.
The next option was to make it a callback in the model, however the problem here is I don’t have access to the current_user inside the callback and again it seems its bad practise to add the current_user helper into a model.
So I am left with perhaps doing it in the controller? Again does not seem the right place for it?
An example to make it a little easier to understand:
I want to allow a User with the role of admin to be allowed to change the status of a post to "resolved", no one else is allowed to change the status to "resolved"
Try this,
create a instance method in User model like bellow,
def is_admin?
self.has_role(:admin) # if you are using rolify
---OR---
self.status == "admin" # if you have status attribute in your user table
end
Then call this method on current_user in edit/update method of post controller. to check current_user is admin or not

Authorization for actions (not models!) with roles in rails

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.

Dynamic roles and permissions system in Rails app

I need to create roles based permissions systems in my Rails app. I would be totally happy with CanCan, but the main problem - it has to be dynamic, so that Admin has to be able to assign permissions and to create new roles. The permissions can be simple controller/action restrictions, and can be data related, for example some users can edit only their own profiles, and some of them can edit the profiles of all the users in the particular group. And it would be really nice to allow Admin to create new permissions.
What I'm thinking about is to store in db a controller/action, and some data related restrictions (I'm really confused here about the way to define them). So could you please give me some advice, what would be the best way to organize permissions?
Any thoughts are much appreciated
If you like CanCan, then I think is best to use it. Here is a short tutorial about storing abilities in database so non-programmers can update them:
https://github.com/ryanb/cancan/wiki/Abilities-in-Database
If you really, really want to implement such system yourself. Depending on your needs, I will suggest for you to implement it as simple as possible.
In the case you need only users to have access to modules(certain controllers). You can do:
Store all users permissions in just like serialized fields -> http://apidock.com/rails/ActiveRecord/Base/serialize/class
class User
serialize :permissions, Array
def access_to?(module)
permissions.include? module.to_s
end
end
some check when setting this field would be nice.
Just make a check on top of every controller if the current user have access to this controller(section)
class ApplicationController
private
def self.require_access_to(module)
before_filter do |c|
unless c.send(:current_user).try :access_to?(module)
c.send :render_no_presmissions_page
end
end
end
end
class AdminNewsController
require_access_to :news
end
Of course this is just a start position, from where you can easily evolve.
[EDIT The link given by #RadoslavStankov is a better answer than this.]
You can do it with CanCan easily. Just make a model for permissions (which has_and_belongs_to_many :users), and in your Ability#initialize load the permissions appropriate to the user from the model and say that the user can do them. Then make appropriate user interface for administrating that model.
Something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
user.permissions.each do |permission|
can permission.symbol, permission.scope
end
user.prohibitions.each do |permission|
cannot permission.symbol, permission.scope
end
end
end
where scope returned something like :all for nil scope, or Object.const_get(#scope_name) otherwise... Play with it. Anyway, it's doable.
More complex CanCan things are likely to be more complex (duh) - for example conditional permissions - but that would be a bitch to administer as well, so I think this should be enough for most applications.

Is it safe to put reference to current user in User model in Rails?

You know, I think I have to check current user in the model callbacks (like before_update). Rather than rely solely on adding where ('something.user_id = ?', 'current_user.id') in the controllers. I need something like Thread.CurrentPrincipal in .NET
Is it safe to put reference to current user in User model? I'm sorry I don't really understand how it works under the hood yet.
Or how you do it The Rails way?
Sorry if this a silly question.
Added on 3/27
Oops
To get the right answer you have to ask the right question. And that's not an easy task in itself. How can it be that other people's questions are so obscure and they get their answers and your own question is so clear-cut but nobody understands it? :)
I do not understand where to put security check. That the user will get access only to his own stuff. On the controller level? And then test every action? "should not /view|create|edit|destroy/ other user's stuff"? I thought may be I can put it in the model and have one place to /write|refactor|test/. That's why I asked about how I can get a reference to the current user.
Actually I'm surprised I didn't find anything relevant in Rails Guides or Blogs. Some questions were asked but no authoritative "best practices" besides "don't do it".
After giving some thought I decided to just create in the controller before_filter the scope scoped to the current user and just rely on my own convention (promised to myself that I won't access the model directly). And just test it once per controller. That's not a banking application anyway.
I'm not sure I get your situation. If you want to check if some other model's instance belongs to current user - use associations (I inferred that from "something.user_id = ?").
Else - in ActiveRecord before_update method is used on a per-instance basis. I.e. you pass current instance to that callback as an argument. Hence:
def before_update(current_user_instance)
current_user_instance.do_something
end
Will yield any user instance as current_user_instance in the callback. So you can do following:
>> user_1 = User.find(1)
>> user_1.update_attribute(:some_attribute, 'some value')
>> user_2 = User.find(2)
>> user_2.update_attribute(:some_attribute, 'some other value')
This will call do_something method on separate instances (user_1 and user_2)
I don't really understand Thread.CurrentPrincipal, but current_user generally means the logged in user and that is completely a controller context. It is not available for use inside a model. So a hacky solution would be:
class UseCase < ActiveRecord::Base
after_save :my_callback_method
attr_accessor :current_user
def my_callback_method
# Some operations based on current_user
end
# ...
end
And then in your controller:
#...
use_case = UseCase.find(use_case_id) # Just an example, can be anything
use_case.current_user = current_user
# then this
use_case.save
# or basically
use_case.method_that_triggers_after_save_callback
#...
Warning: I am sure this is bad practise (I have never used it myself). But this will work. Ruby gurus / MVC gurus out there, please comment.

Model-level authorization in Rails

I want to implement authorization in my Rails application on a model level (not controller), in a similar way that validation on models is done. What is the best way to do this?
If it is implemented in the models itself, the main problem is that the models don't have access to the current user. I've seen solutions like: Thread.current[:user_id] = session[:user_id], but that doesn't seem like a good idea.
I've seen a different approach where variants of the methods like create, find and new are created, accepting an additional parameter for the current user.
Another approach would be to implement all the methods in the User/role class, so for example user.posts.create or user.readable_posts.find would be used instead of Post.create or Post.find.
Which of these approaches would be suggested? Are there any better ways to implement the authorization? Are there any plugins that makes this easier? I need an approach that scales well for multiple roles and models.
I would recommend you to look at declarative authorization. It works with both models and controllers.
The way it do what you are asking is having a before_filter in the applicationController that sets Authorization.current_user = current_user where Authorization is a module.
I think that approach is the best one, it keeps the models clean and you don't have to remember to include the user everywhere, but can filter it in the models callback functions instead.
Why would you do this? Isn't controller level identification enough?
if #user.is_able_to_do_this?
do_this
else
blah!
end
... and in your model
def is_able_to_do_this
if self.rights > Constant::Admin_level_whatever
return true
end
return false
end

Resources