0

I have User, Account, and Role models. Role stores the relationship type between Account and User.

I left attr_accessible in Role blank to prevent a mass assignment vulnerability (otherwise attackers could change the role type--owner, admin, etc...--, account or user ids).

But what if an admin wants to change a subscriber to a moderator? This would raise a mass assignment security exception:

user = User.find(params[:id])
role = user.roles.find_by_account_id(params[:account_id])
role.type = "admin"

How do I solve this? One way is to create a separate model to represent each role (owner, admin, moderator, subscriber) and use an STI type pattern. This lets me do:

user = User.find(params[:id])
user.moderatorship.build(account_id: params([:account_id])

Tedious! I would have to create Onwership, Moderatorship, Subscribership, etc..., and have them inherit from Role. If I want to stick to a single Role model, how can I have dynamic roles without exposing myself to mass assignment vulnerability?

Bonus question: Should I use a User has_many roles (user can have a single record for each role type) or has_one role (user can only have one role record, which must be toggled if their role changes) pattern?

class User < ActiveRecord::Base
  attr_accessible :name, :email
  has_many :accounts, through: roles
end

class Account < ActiveRecord::Base
  attr_accessible :title
  has_many :users, through: roles
end

class Role < ActiveRecord::Base
  attr_accessible
  belongs_to: :user
  belongs_to: :account
end
Mohamad
  • 34,731
  • 32
  • 140
  • 219

2 Answers2

2

You can use "as" with attr_accessible to have different assignment abilities. For instance,

attr_accessible :type, as: :admin

Then, when you do mass assignment, you can do something like

@role.update_attributes {type: :moderator}, as: :admin # Will update type
@role.update_attributes {type: :moderator} # Will not update type
Max
  • 15,157
  • 17
  • 82
  • 127
  • would you be kind enough to tell me the difference between your approach and that of Flexoid (see other answer) – Mohamad Apr 29 '12 at 23:39
  • I don't really see a difference. It just seems like Flexoid's solution moves the logic from the model to the controller, which I wouldn't recommend. (Remember, Rails advocates fat models, skinny controllers.) I think you really have two things going on here. One is attribute authorization (type of role), and the other is model authorization (whose type of role). I wrote a rather detailed post on CanCan and the things you can do with it here: http://stackoverflow.com/questions/7130853/defining-abilities-in-more-complex-environment-with-role-and-group-models/7133839#7133839 – Max Apr 29 '12 at 23:49
  • Also, you can do things such as, given a User u1 you want to modify, you can check that the logged in user is an admin of the group u1 belongs to etc. I definitely think CanCan is the way to go for this. I have tried other, custom built solutions, twice, and both times I went back to CanCan. I would at least suggest reading the wiki. – Max Apr 29 '12 at 23:52
  • thanks, I think I'm going for this approach. One last thing: I'm just not sure whether to stick to a single or multiple rows per role type per user per account. So if User1 will have 3 rows in roles (mod, admin, etc...) or just one row that gets toggled/changed! – Mohamad Apr 30 '12 at 00:28
  • also, I'm assuming you're omitted the need to write a helper method to get the role of curret_user – Mohamad Apr 30 '12 at 00:32
1

The most flexible approach is to override mass_assignment_authorizer method in model class to change accessible attributes dynamically.

For example:

class Article < ActiveRecord::Base
  attr_accessible :name, :content
  attr_accessor :accessible

  private
  def mass_assignment_authorizer
    super + (accessible || [])
  end
end

Now you can use it this way:

@article.accessible = [:important] if admin?

This example are from RailsCast #237, where you can learn more information about this approach.


In addition, I want to recommend you CanCan gem which can help you handle with roles and abilities.

Flexoid
  • 4,155
  • 21
  • 20
  • Thanks. Would you recommend this over using `as: :admi`? Also, I should have asked the question separately, but would you have multiple or single role record per user? (the bonus question) – Mohamad Apr 29 '12 at 21:40
  • As I said, this solution is more flexible and give you ability to integrate mass-assignment protection with any authorization system. Usually I use just one field in user model representing his role and "list" of abitities for each role. For the last I prefer cancan gem which is very easy to use. – Flexoid Apr 29 '12 at 21:51
  • Yeah, I need the roles table. I have many accounts that can be subscribed and moderated by many users. So the single field is not an option. I'm just not sure whether to stick to a single or multiple rows per role type per user per account. Going to let this hang for a but more before I accept the answer. Cheers! – Mohamad Apr 29 '12 at 23:38