Authorizing an array of ID's with the Pundit gem - ruby-on-rails

I have a multiple select box for a has_many association. The params come in as:
foo_ids: ["1", "2", "3"]
Using strong parameters, I do not permit this attribute because I would like to authorize it myself so people cannot just put whatever they want in it.
def update
bar.foos = authorized_foos
bar.update(baz_params)
respond_with bar
end
private
def authorized_foos
foos = Foo.find(params[:baz][:foo_ids])
foos.each do |foo|
authorize foo, :manage?
end
end
This approach is going to force me to find all of the foos, loop through them, and authorize each one individually. Is there an easier way to manage has_many authorization, preferably with the Pundit gem?

The simplest way to do this is looping. Iterate through each user from the users' array and authorize each and every user.
For example,
users_id_array = [1,2,3,4,5,6]
private
def authorized_users
users = User.find(params[:group][:user_ids])
users.each { |u| authorize u, :manage? }
...
end
So this is the very basic and simplest answer.

I do something similar to this. Because of how little info you've provided I'm going to make a swath of assumptions.
You have a many to many association of foos to users. By this I mean a foo can have many users and a user can be a member of many foos.
You understand how to set-up the models to attain (1). If you don't just comment and I'll edit this response.
So what we will need to have working for this to work is the following:
#foo.users returns a collection of users
#user.foos returns a collection of foos
With the models set up to support the actions above this is much easier than you are making it out to be.
class FooPolicy < ApplicationPolicy
class Scope < Scope
def resolve
user.foos
end
end
end
I think you're missing the point of pundit. Pundit allows you to do authorization on a user by user basis. So the above code scopes a user to only the groups (foos in this case) that they are a part of.

Related

limit user only to specific data

I have three types of users "Admin," "Manager," and "Employee." Also, I have an accounts model. All of them can show these accounts. I want to limit employees' access to these accounts. I put the Ids of these accounts in an array and did a limit like the code below. My question is, I had 15 endpoints related to the accounts, Is the best way to do it like this, or may there be a solution that does it without editing all these endpoints?
def index
if #current_user.user_type == 'Admin'
#accounts = Accounts.all
#accounts = optional_paginate(#accounts)
elsif #current_user.user_type == 'Employee'
#accounts = Account.where(id: ACCOUNTS_IDS)
#accounts = optional_paginate(#business_accounts)
else
#business_accounts = optional_paginate(Account.all.includes(:account_managers).exclude_pending)
end
end
The ACCOUNTS_IDS is an array of accounts ids that the employees can access to them only.
Your strategy works, but I think you're sensing that it's not ideal.
You had to hard-code an array of Account IDs from your database, which is never a good idea. If you ever needed to migrate or seed or recreate the database, those IDs could change.
You have this code repeated 15 times(so it's not DRY)
If an account with an id in ACCOUNTS_IDS is ever deleted, imagine what will happen to each of these controller actions.
Adding to or removing from the accounts (ACCOUNTS_IDS) that Employees can see requires you to change your codebase and push those changes.
I'd suggest doing a few things differently:
1. Use an authorization gem
I personally love pundit, but there are other popular options
2. Don't hard-code database information
As mentioned before, the IDs can change and code that relies on a specific record having a specific ID (or relying on that specific record to exist) is brittle.
It's much better to add a column to your table to control this.
Here's an example migration:
add_column :accounts, :restricted, :boolean, default: true, null: false
ACCOUNTS_IDS = [1, 2, 3, 4] #just an e.g.
Account.where(id: ACCOUNTS_IDS).update_column(restricted: false)
:restricted could also be something easier to understand, e.g. :visible_to_employees
Now you can make scopes in your Account model:
# /models/account.rb
class Account < ApplicationRecord
scope :restricted, -> { where(restricted: true) }
scope :unrestricted, -> { where(restricted: false) }
...
end
This allows you to do Account.all to get everything, Account.restricted to get some, and Account.unrestricted to get the others.
This also allows you to add a view form where accounts could be made visible to Employees without having to change the code of your application.
3. Do this in the model, not in the controller
Lean towards having more code in your models and less code in your controllers.
(this will also help you when you implement a Pundit scope)
# models/user.rb
class User < ApplicationRecord
...
def authorized_accounts
case user_type
when 'Admin'
Account.all
when 'Employee'
Account.unrestricted
else
Account.all.includes(:account_managers).exclude_pending
end
end
Now your controller can be greatly simplified:
def index
#accounts = optional_paginate(#current_user.authorized_accounts)
end

Setting custom permissions within a "dashboard" area in rails

I'm putting together a side project for a teacher/student type of website where the student will share a dashboard with the teacher. The teacher and student can both upload files and leave comments in the dashboard.
The part that I'm stuck on is the permission. For student, I've set up the index controller to this method
def index
#Homework = Homework.where(:user_id = current_user)
end
With this, I'm able to have the student only see the work that they have, but I'm confused on how to get the teacher to see each individual student's work?
Suggestions? Thanks
Here's a simple solution if you only ever need to support a single class in your application:
def index
if current_user.teacher?
#homeworks = Homework.all
else
#homeworks = Homework.where(user_id: current_user)
end
end
Otherwise, your Homework schema does not seem to be correctly designed. See your query below:
Homework.where(:user_id = <student_id>)
This works to retrieve a student's homeworks, but it does not work to retrieve a teacher's students' homeworks. You may need a class_id for a teacher to see each individual student's work:
Homework.where(:class_id = <class_id>, :user_id = <student_id>)
A Class has_many Teachers and has_many Students. This design will allow you to support multiple classes.
Some more guiding questions:
Is teacher/student both kept in the same User model?
How do you differentiate between teacher/student in your current User model?
Is there a "user_type" column somewhere in User?
What happens if the current_user is of the "teacher" user_type?
For complex user permissions, use CanCanCan: https://github.com/CanCanCommunity/cancancan
Don't use uppercase instance variables. ex: #Homework should be #homework
Check out the gem CanCan. Install it (follow the instructions, you should have to put something in application controller), Then, put in your ability file:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Homework, user_id: user.id
end
end
Then at the top of your StudentController put
load_and_authorize_resource
And the index action should look like:
#homework = #student.homework
Now, you didn't post your whole controller so this is a much as I can help.
I believe you may have a bigger underlying issue. You have students and teachers has_many homework i read in your comment. Then in your example you use user_id. You are likely overriding your students and teacher ownership of homework. You would need a has_and_belongs_to_many relationship OR you would need a student_id and teacher_id columns on the homework table
Cancan automatically generate a number of instance variables which can make it feel like magic. Watch the free railscasts on cancan the same guy who made the video wrote the CanCan library.

Access current_user from within Rails models?

I am building an Invoicing Application that basically follows the following pattern:
Users < Clients < Projects < Invoices
Now in order to generate autoincrementing invoice numbers for each User I put this in my Invoice model:
before_create :create_invoice_number
def create_invoice_number
val = #current_user.invoices.maximum(:number)
self.number = val + 1
end
However, it seems that the current_user variable cannot be accessed from within models in Rails?
What can I do to solve this problem?
This is due to separation of concerns in Rails and is a somewhat sticky issue to deal with. In the Rails paradigm, models should have no knowledge of any application state beyond what they're passed directly, so most Rails coders will tell you that any model needing to know about a current_user is code smell.
That said, there are three ways to do this, each "more correct" (or at least I would consider them so).
First, try creating an association to the user inside the invoice and link the invoice to the user in the controller:
class InvoicesController < ApplicationController
...
def create
#invoice = current_user.invoices.create(params[:invoice])
...
end
And in your model:
belongs_to :user
def create_invoice_number
self.user.invoices.maximum(:number) + 1
end
If that doesn't work, do this manually in the controller. It's true that controllers should always be as skinny as you can manage, but since this is clearly an application-level concern the controller is the place to put it:
class InvoicesController < ApplicationController
...
def create
#invoice = Invoice.create(params[:invoice])
#invoice.update_attribute(:number, current_user.invoices.maximum(:number))
...
end
Lastly, if you really, really want to bridge the controller and model, you can do so with ActionController::Sweepers. They are not intended for this purpose but will certainly get the job done for you.
there should not be any arise of such case still if you want then make use of observers in rails

RoR: Different user roles for each new created 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!

rails: mass-assignment security concern with belongs_to relationships

I've been reading up on rails security concerns and the one that makes me the most concerned is mass assignment. My application is making use of attr_accessible, however I'm not sure if I quite know what the best way to handle the exposed relationships is. Let's assume that we have a basic content creation/ownership website. A user can have create blog posts, and have one category associated with that blog post.
So I have three models:
user
post: belongs to a user and a category
category: belongs to user
I allow mass-assignment on the category_id, so the user could nil it out, change it to one of their categories, or through mass-assignment, I suppose they could change it to someone else's category. That is where I'm kind of unsure about what the best way to proceed would be.
The resources I have investigated (particularly railscast #178 and a resource that was provided from that railscast), both mention that the association should not be mass-assignable, which makes sense. I'm just not sure how else to allow the user to change what the category of the post would be in a railsy way.
Any ideas on how best to solve this? Am I looking at it the wrong way?
UPDATE: Hopefully clarifying my concern a bit more.
Let's say I'm in Post, do I need something like the following:
def create
#post = Post.new(params[:category])
#post.user_id = current_user.id
# CHECK HERE IF REQUESTED CATEGORY_ID IS OWNED BY USER
# continue on as normal here
end
That seems like a lot of work? I would need to check that on every controller in both the update and create action. Keep in mind that there is more than just one belongs_to relationship.
Your user can change it through an edit form of some kind, i presume.
Based on that, Mass Assignment is really for nefarious types who seek to mess with your app through things like curl. I call them curl kiddies.
All that to say, if you use attr_protected - (here you put the fields you Do Not want them to change) or the kid's favourite attr_accessible(the fields that are OK to change).
You'll hear arguments for both, but if you use attr_protected :user_id in your model, and then in your CategoryController#create action you can do something like
def create
#category = Category.new(params[:category])
#category.user_id = current_user.id
respond_to do |format|
....#continue on as normal here
end
OK, so searched around a bit, and finally came up with something workable for me. I like keeping logic out of the controllers where possible, so this solution is a model-based solution:
# Post.rb
validates_each :asset_category_id do |record, attr, value|
self.validates_associated_permission(record, attr, value)
end
# This can obviously be put in a base class/utility class of some sort.
def self.validates_associated_permission(record, attr, value)
return if value.blank?
class_string = attr.to_s.gsub(/_id$/, '')
klass = class_string.camelize.constantize
# Check here that the associated record is the users
# I'm leaving this part as pseudo code as everyone's auth code is
# unique.
if klass.find_by_id(value).can_write(current_user)
record.errors.add attr, 'cannot be found.'
end
end
I also found that rails 3.0 will have a better way to specify this instead of the 3 lines required for the ultra generic validates_each.
http://ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators

Resources