Search code examples
ruby-on-railsrubyauthorizationrolesmass-assignment

How do I avoid mass assignment vulnerability with dynamic roles?


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

Solution

  • 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