Users and Roles for a Post in Ruby on Rails - ruby-on-rails

I have a number of users who have different roles in relation to a numbers of posts. These roles are owner, editor, viewer, none. Each user may only have one role for a post. I have represented this as a has many through relationship in rails as follows:
class User < ActiveRecord::Base
has_many :roles
has_many :posts, :through => :roles
end
class Post < ActiveRecord::Base
has_many :roles
has_many :users, through => :roles
end
class Role < ActiveRecord::Base
attr_accessor :role
belongs_to :users
belongs_to :posts
end
Where the role attribute is used to indicate which type of role the user has on the post.
When setting a new role I cannot simply use the << operator as it wont set the role attribute. What is the preferred way of handling this situation? How can I enforce that there is only one role per user / post combination and enforce this in my Role creation logic?

You can check in the creation of roles for the User , if he already has a role assigned in which case you can skip assigning this role.
unless user.roles.present?
user.roles.create
end

I understand that you want to make sure that no user will have more than one role for a certain post. If this is what you want to achieve then you just need to add uniquness validation to your Role mode
validates :user_id, uniqueness: {scope: :post_id, message: 'User can have one role per post'}
this will ensure that the combination of user_id and post_id will be unique, you can see more on rails guide on validation with scope.

Related

Rails: Is there any way to build dynamic role based authorization in rails?

I am trying to achieve role-based authorization in Rails.
What we require:
Roles should be dynamic, we should able to create, edit, or delete roles.
Permissions also should be dynamic.
Findings:
We can't use the pundit gem because its policies are static and we can't make it dynamic.
We can use the cancan gem and we can use it dynamically but I didn't get how it can be done? And how it works with `database?
It's my first project on the authorization part. We have rails as the back-end and vue.js as the front end. Whatever roles are there, on the database all data should be empty at first. We'll use the seed to create a super-admin role and give all permissions. Super-admin will create roles, edit roles, destroy roles, and also add permissions, edit and destroy permissions eventually.
If there is any other helpful method then please let me know.
Thanks.
Pundit vs CanCanCan
Your conclusions about CanCanCan and Pundit are just nonsense. Neither of them are "static" or "dynamic" and they have pretty much the same features. The architecture and design philosophy are radically different though.
CanCanCan (originally CanCan) is written as a DSL which was the hottest thing since pre-sliced bread back when Ryan Bates created CanCan 10 years ago. It scales down really well and is easy to learn but gets really ugly as soon as you reach any level of complexity. If anything doing "dynamic authorization" in CanCanCan is going to be a nightmare due its architecture. The ability class in CanCanCan is the god of all god objects.
Pundit is just Object Oriented Programming. In pundit your policies are just classes that take a user and resource as initializer arguments and respond to methods like show?, create? etc. Pundit is harder to understand initially but since its just OOP you can tailor it however you want. And since your authentication logic is stored in separate objects it scales up to complexity far better and adheres to the SOLID principles.
How do I setup a dynamic roles system?
This is you standard role system ala Rolify:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
def has_role?(role, resource = nil)
roles.where({ name: role, resource: resource }.compact).exists?
end
def add_role(role, resource = nil)
role = Role.find_or_create_by!({ name: role, resource: resource }.compact)
roles << role
end
end
# rails g model user_roles user:belongs_to role:belongs_to
class UserRole < ApplicationRecord
belongs_to :user
belongs_to :role
end
# rails g model role name:string resource:belongs_to:polymorphic
class Role < ApplicationRecord
belongs_to :resource, polymorphic: true, optional: true
has_many :user_roles
has_many :users, through: :user_roles
end
You can then scope roles to resources:
class Forum < ApplicationRecord
has_many :roles, as: :resource
end
Rolify lets you go a step further and just defines roles with a class as the resource. Like for example user.add_role(:admin, Forum) which makes the user an admin on all forums.
How do I create a permissions system?
A simple RBAC system could be built as:
class Role < ApplicationRecord
has_many :role_permissions
has_many :permissions, through: :role_permissions
def has_permission?(permission)
permissions.where(name: permission).exists?
end
end
# rails g model permission name:string
class Permission < ApplicationRecord
end
# rails g model role_permission role:belongs_to permission:belongs_to
class RolePermission < ApplicationRecord
belongs_to :role
belongs_to :permission
end
So for example you could grant "destroy" to "moderators" on Forum.find(1) by:
role = Role.find_by!(name: 'moderator', resource: Forum.find(1))
role.permissions.create!(name: 'destroy')
role.has_permission?('destroy') # true
Although I doubt its really going to be this simple in reality.
If I understand your requirements correctly, you should be able to use Pundit to achieve this.
From what I understand,
Users have roles
Users can be assigned and unassigned roles at runtime
Permissions are given to either roles or users directly.
Permissions can be updated at runtime
So you can have something like,
class User
has_many :user_role_mappings
has_many :roles, through: :user_role_mappings
has_many :permissions, through: :roles
...
end
class UserRoleMapping
belongs_to :user
belongs_to :role
end
class Role
has_many :role_permission_mappings
has_many :permissions, through :role_permission_mappings
...
def has_permission?(permission)
permissions.where(name: permission).exists?
end
end
class RolePermissionMapping
belongs_to :role
belongs_to :permission
end
class Permission
...
end
And in your policy, you can check if any of the user's roles has the required permission.
class PostPolicy < ApplicationPolicy
def update?
user.roles.any? { |role| role.has_permission?('update_post') }
end
end
Edit:
Using the mapping tables, you can update the permissions for a role, and the roles for a user from an admin dashboard.
You can achieve dynamic roles with pundit. pundit let's you define plain Ruby objects with methods that are called to determine if a user has permission to perform an action. For example:
class PostPolicy < ApplicationPolicy
def update?
user.has_role('admin')
end
end
If you want a user to have multiple roles you could set up a has_and_belongs_to_many: :roles association on your User model.
See: https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association

Ruby on Rails multiple associations between two models

Here is my problem:
I have a model User. a User may be admin.
A user can send error report to a pool of reports and this report can be assigned to an admin
How would you link these two models (User/Report) knowing that an admin is a user?
I'd want a report.sender and a report.admin but I can't find how to do so.
Thanks!
You can define the relations like this:
class Report < ActiveRecord::Base
belongs_to :reporter, foreign_key: reporter_id, class_name: 'User'
belongs_to :admin, foreign_key: admin_id, class_name: 'User'
And use them like that:
report.admin # => returns a User object
report.reporter # => also returns a User object

Rails Devise - Admin role, model vs attribute

I know how to create an admin role/user : https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-role
What I am wondering though is if there are any advantages or disadvantages to the two options to consider when deciding between them. Can anyone supply any insight on this please?
Let me muddle the water a bit. I prefer to this via a Role table and a join table UserRole. This way I can define more than one role without adding another column/table to db.
class User
has_many :user_roles
has_many :roles, :through => :user_roles
def role?(role)
role_names.include? role
end
def role_names
#role_names ||= self.roles.map(&:name)
end
def role=(role)
self.roles << Role.find_or_create_by_name(role)
end
end
class UserRole
# integer: user_id, integer: role_id
belongs_to :user
belongs_to :role
end
class Role
# string: name
has_many :user_roles
has_many :users, :through => :user_roles
end
It really depends on what you wish to do with your admin role. The first option, I would say is a bit secure as the admin role is a unique model in itself.
The second option is straightforward and would help you get going with the least effort. However, if your users figure out the boolean variable and a way to set it, any user can become an admin and access areas you don't want them to.

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
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
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.

Ruby on rails with different user types

I'm trying to build a application that has different kinds of users, I'm using authlogic for user authentication.
So I have one user model that has the required field for authlogic to do its magic. I now want to add a couple of different models that would describe the extra fields for the different kinds of users.
Lets say that a user registers, he would then select his user type, when he is done registering he would be able to add information that is specific for his user model.
What would be the best way to do this? I am currently looking into polymorphic models but I'm not sure that's the best route to take. Any help would be greatly appreciated, thanks.
You can create different profile tables and just tie the profile to the user. So for each user type you can create a table and store the specific info there and have a user_id column to point back to users.
class User < ActiveRecord::Base
has_one :type_1
has_one :type_2
end
class Type1 < ActiveRecord::Base
belongs_to :user
end
class Type2 < ActiveRecord::Base
belongs_to :user
end
Now this isn't very DRY and could lead to problems if you are constantly adding user types. So you could look into polymorphism.
For polymorphism, the users table would define what type the user is (profileable_id and profileable_type). So something like this:
class User < ActiveRecord::Base
belongs_to :profileable, :polymorphic => true
end
class Type1 < ActiveRecord::Base
has_one :user, :as => :profileable
end
class Type2 < ActiveRecord::Base
has_one :user, :as => :profileable
end
Then there is a third option of STI (single table inheritance) for the user types. But that doesn't scale well if the user type fields differ dramatically.
The best approach I saw it here
http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/

Resources