Pundit with namespaced controllers - ruby-on-rails

The policy_scope works perfectly finding the correct policy named Admin::RemittancePolicy but authorize method not.
module Admin
class RemittancesController < AdminController # :nodoc:
...
def index
#remittances = policy_scope(Remittance).all
render json: #remittances
end
def show
authorize #remittance
render json: #remittance
end
...
end
end
Take a look at output error:
"#<Pundit::NotDefinedError: unable to find scope `RemittancePolicy::Scope` for `Remittance(...)`>"
Perhaps a error with pundit, I really not know how fix it. Thanks.
More information below:
# policies/admin/admin_policy.rb
module Admin
class AdminPolicy < ApplicationPolicy # :nodoc:
def initialize(user, record)
#user = user
#record = record.is_a?(Array) ? record.last : record
end
def scope
Pundit.policy_scope! user, record.class
end
class Scope # :nodoc:
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope.is_a?(Array) ? scope.last : scope
end
def resolve
scope
end
end
end
end
# controllers/admin/admin_controller.rb
module Admin
class AdminController < ActionController::API # :nodoc:
include Knock::Authenticable
include Pundit
before_action :authenticate_user
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
# def policy_scope!(user, scope)
# model = scope.is_a?(Array) ? scope.last : scope
# PolicyFinder.new(scope).scope!.new(user, model).resolve
# end
def policy_scope(scope)
super [:admin, scope]
end
def authorize(record, query = nil)
super [:admin, record], query
end
end
end

Your stacktrace says the error comes from
app/policies/admin/admin_policy.rb:9:in 'scope'
That's this:
def scope
Pundit.policy_scope! user, record.class
end
record.class evaluates to Remittance, so if I understand what you're trying to do, you need to change scope to
def scope
Pundit.policy_scope! user, [:admin, record.class]
end

Related

Refactoring the class component of submitted parameters

The following pattern is encountered:
params[:vehicle][:user_id]
params[:location][:user_id]
params[...etc...][:user_id]
what syntax allows the creation of a method that inputs the current class as the symbol within the param ? for example
class VehiclesController
def edit
v = Vehicle.where('user_id = ? AND user_id = ?', params[:vehicle][:user_id], current_user.id).first
end
class LocationsController
def edit
l = Location.where('user_id = ? AND user_id = ?', params[:location][:user_id], current_user.id).first
end
You can add a method to ApplicationController:
class ApplicationController < ActionController::Base
private
def form_params
params[controller_name.singularize]
end
end
and use it in other controllers:
class VehiclesController < ApplicationController
def create
form_params # => #<ActionController::Parameters {"user_id"=>"1"} permitted: false>
end
end
For permitted params, could be this:
class ApplicationController < ActionController::Base
private
def form_params
params.require(controller_name.singularize).permit(permitted_params)
end
def permitted_params
[] # nothing is permitted by default
end
end
class VehiclesController < ApplicationController
def create
form_params # => #<ActionController::Parameters {"user_id"=>"1"} permitted: true>
end
private
# Override `permitted_params`
def permitted_params
# FIXME: Seems `user_id` is not needed when you have `current_user.id`.
# Besides, it is bad to expose current `user_id` in the form,
# because I can start messing with it and start submitting
# different ids in your forms; like a payment form, make someone
# else pay for my charges.
[:user_id]
end
end

Pundit policy for personalized routes

I'm working on a rails app where I wrote a personalized route called "all_designs"; with the corresponding method on the controller and the view, before I add pundit to my project it was working fine.
Now I'm having this error:
Pundit::AuthorizationNotPerformedError in DesignsController#all_designs
I understand that I'm missing a policy for this action, but the way I'm trying is not working.
How can I add a policy for this method?
Controller:
class DesignsController < ApplicationController
before_action :set_design, only: [:show,:edit,:update,:destroy]
def index
#designs = policy_scope(Design.where(user: current_user, status: 'activo'))
#user = current_user
end
def all_designs
#designs = Design.where(user: current_user)
#user = current_user
end
...
end
Policy:
class DesignPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def create?
true
end
def show?
true
end
def destroy?
user == record.user
end
def update?
# If the user is the owner of the design
user == record.user
end
def all_designs?
true
end
end
I would consider separate controller and policy for this as what you're doing is really just a nested route (designs belonging to a singleton resource).
scope 'user', module: :users do
resources :designs, only: :index
end
module Users
class DesignsPolicy
class Scope < Scope
def resolve
#user.designs # make sure user has a `has_many :designs` assocation.
end
end
end
def index?
true
end
end
# Represents designs belonging to the current user
module Users
class DesignsController < ApplicationController
# GET /user/designs
def index
#designs = policy_scope
end
end
end
This lets you separate the logic of displaying the the current users designs from /designs which would display everything in a clean way.
Every method on the controller which needs to be authorized, needs to contains an explicit declaration like this:
def all_designs
#designs = Design.where(user: current_user)
#user = current_user
authorize #designs
end
The reason it wasn't working was: I missed the authorize line

Rails_admin and rails_admin_pundit error after upgrade to 5.2.1

NoMethodError at /
protected method `policy' called for #<RailsAdmin::MainController:0x007ff5e5d1a528>
Did you mean? policies
Here's the first thing it looks at in the error page (this is in the gemfile code)
# This method is called to find authorization policy
def policy(record)
begin
#controller.policy(record)
rescue ::Pundit::NotDefinedError
::ApplicationPolicy.new(#controller.send(:pundit_user), record)
end
end
private :policy
Getting this error when I try to visit /admin - nothing changed, was working fine in 5.1.6.. I didn't change the policy.rb file, i didn't change any controller code, nothing was changed at all apart from a gemfile update to go to rails 5.2.1
My application policy..
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
def rails_admin?(action)
case action
when :dashboard
#user.admin?
when :index
#user.admin?
when :show
#user.admin?
when :new
#user.admin?
when :edit
#user.admin?
when :destroy
#user.admin?
when :export
#user.admin?
when :history
#user.admin?
when :show_in_app
#user.admin?
else
raise ::Pundit::NotDefinedError, "unable to find policy #{action} for #{record}."
end
end
end
I don't know what caused this to happen.. can't find anything on Google and the gem rails admin pundit was last updated a year ago.
Make sure you have
config.parent_controller = "::ApplicationController"
on your config/initializers/rails_admin.rb, to indicate who's the parent controller. This solved it for me.

Scope for nested resources using pundit resolve method

I am referring to my own question Rails Nested Resources with Pundit Allowing Index and finally came up with a working solution but is there not any much better solution defining scope.where(?) or scope.select(?) in the property_policy? How to get all the properties that only belongs to one specific deal using the pundit resolve method?
What I finally did :
properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_deal, except: [:index, :all]
before_action :set_property, only: [:show, :edit, :update, :destroy]
def all
#properties = Property.all
authorize #properties
end
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(Deal)
end
def set_deal
#deal = Deal.find(params[:deal_id])
# pundit ######
authorize #deal
###############
end
(...)
end
property_policy.rb
class PropertyPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all if user.admin?
end
def all?
user_is_admin?
end
def user_is_admin?
user.try(:admin?)
end
(...)
end
What I'd like better:
properties_controller.rb
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(Property) # => for # #properties = #deal.properties
authorize #deal
end
and in the property_policy.rb something like
def resolve
# scope.where(???) if user.admin? # only an admin user can see the #deal.properties
# or any other solution using scope
end
As a reminder 1 deal has many properties and 1 property belongs to one specific deal. My routes are nested deals/id/properties except for the full list of properties I have simple "/properties". Thanks a lot for helping.
** UPDATE **
I finally went for
properties_controller.rb
def index
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(#deal.properties)
authorize #properties, :index?
end
and in property_policy.rb
class PropertyPolicy < ApplicationPolicy
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.none
end
end
def index?
user_is_admin?
end
def user_is_admin?
user.try(:admin?)
end
end
Not sure if it is the proper way
What you want to do is pass a scope to the policy - not just a class.
#properties = policy_scope(#deal.policies)
class PropertiesPolicy
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.none
end
end
end
Another problem with your controller is that authorize #deal will call DealsPolicy#index? which is not what you want.
To authorize an index action you want to call authorize with the model class (and not an instance):
def index
authorize Property # calls PropertiesPolicy#index?
#deal = Deal.find(params[:deal_id])
#properties = policy_scope(#deal.properties)
end
In that case you don't have to do anything special in your Scope#resolve method really. Just return scope since you can assume at that point that the user is an admin.

pundit_user: undefined method `current_user' for #<User:0x007fcefbc2b150>

I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks

Resources