I am currently setting up my role management in my Rails app with the Rolify gem - both with the latest versions.
In my case, a user can only have one role at the same time for one specific resource. This means, that before I do a
user.add_role :lead, #resource
I would like to delete all maybe already existing roles. Unfortunately something like
user.current_role.remove #resource
is not existing. I only could loop through all maybe existing roles, check if it is existing and delete it. This sounds ugly to me. Stuff like
user.roles = []
does not help either me because i want to delete all roles for a specific resource.
Is there any standard functionality in rolify to support something like this?
Thanks for your help in advance!
Callback methods to the rescue!
class User < ActiveRecord::Base
rolify before_add: :before_add_method
def before_add_method(role)
# do something before it gets added
end
end
I ended up wanting a more substantial solution which removed all kinds of roles from the resource. I made a gist out of it:
user.rb
class User < ApplicationRecord
rolify :strict => true, :before_add => :before_add_role
#Helper method to remove any existing role this user has for a resource
def remove_all_roles resource
# README: This syntax relies on changes on the following PR
# https://github.com/RolifyCommunity/rolify/pull/427
# Or include the source of this directly:
# gem 'rolify', :git => "git://github.com/Genkilabs/rolify.git"
remove_role nil, resource
end
protected
#ensure that we only have a single role per resource
def before_add_role(role)
if role.resource
Rails.logger.debug "User::before_add_role: Adding the role of #{role.name} for #{role.resource_type} #{role.resource_id} to user #{id}"
#remove any pre-existing role this user has to the resource
remove_all_roles role.resource
end
end
end
Related
I am trying to automatically set created_by in my models to currently authenticated user without explicitly passing down user as an argument. I am using devise for authentication and for setting user globally on each request I am using new rails api ActiveSupport::CurrentAttributes like so:
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
# config/initializers/warden.rb
Warden::Manager.after_set_user { |user, auth, opts| Current.user = user }
Then I have Owned concern
# app/models/concerns/owned.rb
module Owned
extend ActiveSupport::Concern
included do
belongs_to(
:created_by,
class_name: "User",
foreign_key: "created_by_id",
default: -> { Current.user }
)
end
end
which is included in models in which I want to automatically set created_by.
It works but recently I got bug where created_by was assigned to wrong person and I am wondering if it could be caused by code I described above because its not thread safe. I am unable to reproduce the bug again and I don't know how to test for such potential race conditions. So my questions are: Is my usage of CurrentAttributes correct and my code in general? Is there a better way how to automatically set created_by?
I want create roles in my project. Each user can be: admin, registered or demo. Each role see different things.
How can I do that? What is the best gem to do roles?
This is a example in 'bad programming" of what I want:
def index
if current_user.role[:name] == 'admin'
#installations = Installation.all
elsif current_user.role[:name] == 'registered'
#installations = current_user.installations
elsif current_user.role[:name] == 'demo'
#installations = current_user.installations.first
else
end
end
Some gems that might be interesting for you :
rolify
role_model
If you decide to implement it yourself, then within some page you might want to change the content, for that you might want to do something like this :
Add a role to the user model using a migration :
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string, default: :demo
end
end
Then in your app you can use it as follows:
def index
case current_user.role
when :admin
#installations = Installation.all
when :registered
#installations = current_user.installations
else
#installations = current_user.installations.first
end
end
You can also simply create a boolean admin for instance.
What you might want to do also is create some methods in your model so that you can call current_user.admin? or current_user.registered? . You can do that by doing (if you chose to use a string to store the role):
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
def registered?
self.role == "registered"
end
end
One advantage I see of having a role stored in a string is that if you have 5 roles for instance then you do not have 4 booleans (as when you store admin in a boolean) but only one string. On the long run you might want to store actually a role_id instead of a string and have a separate role model.
An excellent alternative pointed out by Jorge de Los Santos (another answer) is to use enum :
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
It is an excellent alternative because it will automagically add the methods described above such as current_user.admin? without hard coding them.
With your roles, you might want to do some authorization (admins can have access to specific pages, demo users are restricted to only a subset of pages, etc.). For this, you can use the gem called cancancan. You can look at this railscast to learn more about it. Also, you can have some infos here : How to use cancancan? .
There are plenty of solutions available to you.
Starting by gems:
https://github.com/RolifyCommunity/rolify
https://github.com/martinrehfeld/role_model
By using Devise architecture (in case you use it):
https://github.com/plataformatec/devise/wiki/How-To:-Add-a-default-role-to-a-User
By using enums in rails 4:
class AddRolesToUser < ActiveRecord::Migration
#add_column 'role', :integer, default: 0 to the users table
end
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
That will enable role methods.
user = User.find(1)
user.role #:demo
user.admin? #false
user.registered? #false
And consequently:
if user.admin?
#somethig
elsif user.registered?
#another something
else
#another another something.
And last but not least, what you are searching is not the manage roles solution, is the manage permissions solutions:
https://github.com/ryanb/cancan
Add a boolean, :admin to your User model.
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, deafult: false
end
end
Create a method for a registered user to separate them from demo users, such as verifying their email, providing a home address and phone number, filling out a profile, etc. This is up to you though, first you need to decide how a registered and demo user should be different.
The CanCan gem adds authorization to your project, and is especially useful if you want to implement multiple roles with differing abilities. When used with an authentication system like devise, you get a full suite of capability for your site.
You're in full control of what roles you want to define and what abilities they have. CanCan manages tracking, assignment, and querying of roles, and then gets out of your way to let you build what you need.
You can find the CanCan gem in Github: https://github.com/ryanb/cancan
It's simple to use, and the documentation is straightforward and easy to follow.
I'm a newbie to the ROR world and I'm trying to use ActiveAdmin to design the Admin Panel for an Artists Portfolio Website.
The idea is that each artist has a login/password and can manage assets.
The models are set with has_many in the AdminUser table, and belongs_to in the linked models.
For example, an AdminUser has_many Videos.
There are many linked assets.
What would be the best approach so that:
The currently logged in AdminUser only has access to his own assets?
The currently logged in AdminUser is set as the admin_user_id field
for each newluyy created asset?
Thank you very much for your help!
I am not sure if this answer is in time.
Q1. The currently logged in AdminUser only has access to his own assets.
A1. use cancan for your authorization framework. By default ActiveAdmin doesn't provide the authorization ability for you. so , the key code may looks like:
# in your app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :read, Asset, :admin_user_id => user.id
end
end
for more detailed steps of using Cancan (very easy, you can deal with it in 15 minutes ), please refer to : https://github.com/ryanb/cancan
Q2. The currently logged in AdminUser is set as the admin_user_id field for each newluyy created asset.
A2: AdminUser is just a "User" model. so, you can call it "current_user" in your view and controller as the regular Devise's way. e.g.
#in your controller
def create_xx
# save the admin_user_id to the newly created asset.
Asset.create(:admin_user_id => current_user.id)
end
for more details of Devise, see its official website: https://github.com/plataformatec/devise
I am trying to implement specific object (row) authorisation using cancan, I want it to work in a way that a user can only make a change(update/edit) to a Record if he/she has the role for that specific Record. after consulting the cancan docs I tried doing the following:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Record do |record|
user.can_edit(record)
end
end
end
class User
has_many :assignments
has_many :roles_as_editor, :through => :assignments, :class_name => "Role", :source => :role, :conditions => {:edit => true}
def rec_as_editor
self.roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
end
def can_edit(rec)
rec_as_editor.include?(rec)
end
end
The can_edit method takes in a Record object and ensures that a User has the role necessary to make a change to it by returning true or false. this method is tested and works correctly so the problem seems to be with the CanCan code because when i try editing a Record that the user dosent hold the role for it still allows me to make changes to the Record, anyone know why this wont work?
If you require any further information please let me know through a comment.
Thank You
Are you authorizing the resource in the controller?
you should have load_and_authorize_resource in your controller
or
def edit
#critical_process = CriticalProcess.find(params[:id])
#this here is what you use
authorize! :edit, #critical_process
end
in your edit method inside the critical process controller.
I personally prefer to keep this logic completely separate from models so that I don't have to dig into model code to find authorization issues. In other words, user.can_edit checks for authorization which is what the ability file is supposed to be in charge of. Shouldn't matter though... in this case I think you might have a problem inside the can_edit method. I have used code that looks nearly identical to yours without problems many times like this:
can :manage, Record do |record|
user.has_role?(:record_manager)
end
I suggest including your code for can_edit or use the debugger to see what value gets returned from can_edit.
I think the problem comes from the way you query for the records that are supposed to have the user as an editor.
I copy/pasted your code and built the other associations from scratch.
And testing it in the console it works as expected when I use it:
>> u = User.last
>> a = Ability.new(u)
>> a.can :edit, Role.last
false
The only thing I changed is the query for the records: it seemed to look for a record that owns the role (your Role has a record_id) but then looks for the same key under cp_secondary_id.
I think something is wrong in your query, but what depends on your schema and associations:
roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
as I understood your code we are traversing associations like this:
User=>Assignment<=Role(boolean edit flag)<=Record
I want to make a record management system. The system will have 4 different user roles: Admin, Viewer, Editor and Reviewer.
While the first two are easy to implement using gems such as cancan and declarative authorization, the other two are not so simple.
Basically each new record is created by an Admin (only an Admin can create new records), and should have its own separate Editor and Reviewer roles. That is, a user can be assigned many different roles on different records but not others, so a user might be assigned Editor roles for Record A and C but not B etc.
Editor: can make changes to the record, and will have access to specific methods in the controller such as edit etc.
Reviewer: will be able to review (view the changes) made to the record and either approve it or submit comments and reject.
Viewer: Can only view the most recent approved version of each record.
Are there any ways of handling such record-specific user roles?
This can be accomplished without too much effort with the cancan gem and a block condition. A block condition checks for authorization against an instance. Assuming your Record class had an editors method that returns an array of authorized editors the cancan ability for updating a Record might look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
...
can :update, Record do |record|
record.editors.include?(user)
end
...
end
end
See "Block Conditions" on the CanCan wiki:
https://github.com/ryanb/cancan/wiki/Defining-Abilities
Update
Storing which users have which access to which records could be done many ways depending on your specific needs. One way might be to create a model like this to store role assignments:
class UserRecordRoles < ActiveRecord::Base
# Has three fields: role, user_id, record_id
attr_accessible :role, :user_id, :record_id
belongs_to :user_id
belongs_to :record_id
end
Now create a has_many association in the User and Record models so that all role assignments can be easily queried. An editors method might look like this:
class Record < ActiveRecord::Base
...
has_many :user_record_roles
def editors
# This is rather messy and requires lot's of DB calls...
user_record_roles.where(:role => 'editor').collect {|a| a.user}
# This would be a single DB call but I'm not sure this would work. Maybe someone else can chime in? Would look cleaner with a scope probably.
User.joins(:user_record_roles).where('user_record_roles.role = ?' => 'editor')
end
...
end
Of course there are many many ways to do this and it varies wildly depending on your needs. The idea is that CanCan can talk to your model when determining authorization which means any logic you can dream up can be represented. Hope this helps!