Ruby On Rails Pundit Gem Authorizing Dashboard - ruby-on-rails

How would I provide pundit authorization for a dashboard controller which provides data from various models?
My DashboardsController looks like this:
class DashboardsController < ApplicationController
before_action :authenticate_user!
before_action :set_user
before_action :set_business
after_action :verify_authorized
def index
end
private
def set_user
#user = current_user
end
def set_business
#business = current_user.business
end
end
How would I authorize for both #user and #business within my DashboardsPolicy?

I would argue that trying to get access to a dashboard is not a policy based on a resource named dashboard, but simply a special method in the business policy.
Therefore, I would add this to the BusinessPolicy as a method dashboard.
# in your controller
authorize #business, :dashboard?
# and the business_policy
class BusinessPolicy < ApplicationPolicy
def dashboard?
# condition depending on a `user` (current_user) and a record (business)
user.admin? || user.business == record
end
end
Or it might be even simpler. If someone is allowed to see the dashboard when she is allowed to show the business, then just re-use BusinessPolicy#show? in your controller:
authorize #business, show?

Pundit expects a current user and a model object to be passed to it. In this case I think what you would want is a DashboardsPolicy class, and you would authorize it like:
def index
authorize(#business)
end
From the README:
Pundit will call the current_user method to retrieve what to send into
this argument
The authorize method automatically infers that Post will have a
matching PostPolicy class, and instantiates this class, handing in the
current user and the given record
There is also a specific section in the README regarding headless policies that uses the Dashboard as the example action: https://github.com/varvet/pundit#headless-policies
You can also create a plain ruby object that takes two entities and use that as your object to authorize:
class UserBusiness
def initialize(user, business)
end
...other methods here
end
#model = UserBusiness.new(user, business)
authorize(#model)

Related

Pundit: authorize actions within namespaced controllers

I have a Blog model which has different states. In order to keep a skinny controller and follow the convention of only having CRUD operations per controller, I followed DHH's namespacing controllers pattern and namespaced out the Blog controller.
Now I have a Blogs::NewDraft controller, a Blogs::AwaitingApproval controller, and a Blogs::Active controller.
The issue is with writing my policies to authorize the actions within these namespaced controllers. All the actions in all the namespaced controllers are all authorizing the same Blog model object. The issue is that I need each of the namespaced controllers to authorize in a matching namespaced policy (as opposed to all of the namespaced controllers authorizing within the same blog_policy.rb file.)
Basic Example: For a restful resource with a restful controller that is NOT namespaced you do it something like this:
#app/controllers/blogs_controller.rb
class BlogsController < ApplicationController
def index
authorize :blog
#blogs = Blog.all
end
def show
#blog = Blog.find(1)
authorize #blog
end
end
And now the matching Policy
#app/policies/blogs_policy.rb
class BlogPolicy < ApplicationPolicy
def index?
user.admin?
end
def show?
record.author == current_user
end
end
You do it like that when you don't namespace.
Current Code to Try to get Namespacing to work with Pundit: I am namespacing. I am still authorizing a Blog object, but I need to authorize the actions within each namespaced controller within a namespaced policy:
#app/controllers/blogs/new_drafts.rb
class Blogs::NewDraftsController < ApplicationController
def index
# doesn't work
authorize Blog::NewDrafts
#blogs = Blog.new_drafts
end
def show
#blog = Blog.find(1)
#doesn't work either
authorize #blog, Blog::NewDraft
end
end
So I want that namespaced controller to NOT route to app/policies/blog_policy.rb, but instead to app/policies/blogs/new_draft_policy.rb
#app/policies/blogs/new_draft_policy.rb
class Blogs::NewDraftPolicy < ApplicationPolicy
def index?
user.admin?
end
def show?
# the record is a blog from the Blog Model
record.author == current_user
end
end
Pundit Documentation and Usage
Don't know how to route to namespaced policy AND pass in the Blog record. However: below is how you do it when your namespaced policy is able to authorize only based on the current user's permissions/roles:
#app/controllers/blogs/new_drafts.rb
class Blogs::NewDraftsController < ApplicationController
def index
authorize [:blogs, :new_draft]
#blogs = Blog.new_drafts
end
end
#app/policies/blogs/new_draft_policy.rb
class Blogs::NewDraftPolicy < ApplicationPolicy
def index?
user.admin?
end
end

authorize with a parent resource in Pundit

I have a Campaign model which has many Applicants. I'm currently nesting Applicants within Campaigns. I'm trying to authorize a user to applicants#index based on if they are the owner of the campaign.
resources :campaigns do
..
resources :applicants do
..
end
end
What's the best way to secure the applicants#index action in Pundit? Ideally I would like to pass the #Campaign to authorize.
class ApplicantsController < ApplicationController
def index
#applicants = Applicant.where(campaign: #campaign)
authorize #campaign
respond_with(#applicants)
end
But this results in Pundit looking for campaign_policy.
I'd probably use the show? method on the CampaignPolicy
authorize #campaign, :show?
where show? (call it whatever you want...manage?, etc...) would be
def show?
record.user_id = user.id
end
Just because the resource being displayed is a list of applicants doesn't mean you need to authorize against them directly. If your logic requires authorizing the owner of the campaign, do that.
Finally, if this is some wide-spread, common thing in your application, you might consider creating some value object to wrap your current user and campaign in.
class CurrentState < Struct.new(:user, :campaign); end
and then override the pundit_user method.
def pundit_user
CurrentState.new(current_user, Campaign.find(params[:campaign_id])
end
See Additional Context in the Pundit docs.

Pundit: undefined method `authorize'

I am trying to use Pundit to authenticate access to some static views that require no database interaction:
class StaticController < ApplicationController
include Pundit
authorize :splash, :home?
def home end
end
Below is my static policy. The home? policy always returns true, so I should be able to access the home view.
class StaticPolicy < Struct.new(:user, :static)
def initialize(user, resource)
#user = user
#resource = resource
end
def home?
true
end
end
Instead I get this:
undefined method `authorize' for StaticController:Class
Pundit works perfectly if I'm authorizing a model:
def forums_index
#forums = Forum.all
authorize #forums
end
However, if I try to use the authorize method outside of an action that doesn't make use of a model I get:
undefined method `authorize' for StaticController:Class
Well, AFAIK you'll always have to authorize against either an object or a class, while CanCan already "load_and_authorize_resource", when using Pundit you already know that you have to load and authorize something yourself (sorry if I'm being too obvious here).
That said and considering that your view doesn't have DB interation, it seems to me that the best solution for your case is make some custom authorization against your user, something like
class StaticPolicy < Struct.new(:user, :static)
def initialize(user, resource)
#user = user
#resource = resource
end
def home?
authorize #user, :admin # or suppress the second parameter and let the Policy use the 'home?' method
true
end
end
and in your UserPolicy something like
class UserPolicy < ApplicationPolicy
def admin # or def home?, it's up to you
user.admin?
end
end
I didn't test it, but that's the main idea, does it make any sense? Is it clear?
Please give it a try and post any impressions, hope it helps :)

Get current_user in Rails form validation by defining a virtual attribute

Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.
I found this answer in a similar question:
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`
How can I set this up with my post and user controller so that the current_user variable is accessible in the model?
Solution:
This whole thing is wrong from an application design perspective as #Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.
The "similar question" is saying you can do something like this
class YourModel < ActiveRecord::Base
attr_accessor :current_user
# ...
end
and then in your controller action you can do something like
#your_model = YourModel.find(params[:id])
#your_model.current_user = current_user
#your_model.assign_attributes(params[:your_model])
if #your_model.valid?
# ...
You can then use self.current_user within YourModel's validation methods.
Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.
As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb
class YourModelPolicy < Struct.new(:user, :your_model)
def update?
user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
end
end
Include Pundit in your ApplicationController
class ApplicationController < ActionController::Base
include Pundit
# ...
end
Then, in your controller action you can do simply
def update
#your_model = YourModel.find(params[:id])
authorize #your_model
# ...
The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.
Authorization shouldn't be done in models. Models have already many responsibilities don't you think?
That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:
authorize! :create, Post
You can define a "virtual attribute" in your model like this:
class Book < ActiveRecord::Base
attr_accessor :current_user
end
Its value can be set directly in your controller like this:
class BooksController < ApplicationController
def create
book = Book.new
book.current_user = current_user
book.save!
end
end
And inside your model's validation routine, you can access it like any other ActiveRecord field:
def validate_user_permission
errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end
I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:
def create
Book.create!(current_user: current_user)
end
In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:
attr_accessible :current_user
I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.
If you don't need all the power of a gem like CanCan, you can role your own.
class BooksController < ApplicationController
before_filter :gold_required, :only => :create
def create
book = Book.new
book.save!
end
# Can be application controller
private
def gold_required
return current_user && current_user.is_gold?
end
end
You may want to put the filter on the 'new' method as well.

Rails 3 authorization with default auth

I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end

Resources