How to make pundit policies more DRY? - ruby-on-rails

In one of my project I started to using pundit gem and I have a very simply policy that looks like this:
class CompanyPolicy < ApplicationPolicy
def index?
true if user.is_a? Administrator
end
def new?
true if user.is_a? Administrator
end
def create?
new?
end
def edit?
true if user.is_a? Administrator
end
def update?
edit?
end
end
And the question is how can I avoid repeating this:
true if user.is_a? Administrator

I do trick which looks like this:
class ApplicationPolicy
private
def self.permit_owner_to(*actions)
actions.each do |action|
define_method("#{action}?") do
owner?
end
end
end
def owner?
# owner logic
end
end
And used it in other policies
class ItemPolicy < ApplicationPolicy
permit_owner_to :show, :update, :destroy, :confirm
end

I don't actually think you need to remove this. By repeating this you are explicitly saying that this user must be an administrator to access this method. If you did want to though, you could just create a private method.
class CompanyPolicy < ApplicationPolicy
def index?
admin?
end
def new?
admin?
end
def create?
new?
end
def edit?
admin?
end
def update?
edit?
end
private
def admin?
user.is_a? Administrator
end
end
Guess this is a matter of personal preference.

You could use alias_method.
class CompanyPolicy < ApplicationPolicy
def index?
user.is_a? Administrator
end
alias_method :create?, :index?
alias_method :update?, :index?
end
You have a base class ApplicationPolicy which probably already contains:
def new?
create?
end
def edit?
update?
end
so you don't need to repeat these methods in your subclass.
.is_a? returns true or false so no need to explicitly return true if true.
That's a lot more succinct eh? :)

I combined answers from above and came up with the following:
class ApplicationPolicy
attr_reader :user
def initialize(user)
#user = user
end
def self.permit(roles, options)
return if options[:to].none?
options[:to].each do |action|
define_method("#{action}?") do
return #user.roles? Array.wrap(roles) if options[:when].blank?
send(options[:when]) and #user.roles? Array.wrap(roles)
end
end
end
end
which allows one to use it like this:
class CommentPolicy < ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#record = record
super(user)
end
permit %i[admin member], to: %i[show edit destroy update], when: :created_by_user
def created_by_user
#record.user == #user
end
end
and
permit :admin, to: %i[index update edit]
works as well
my roles method from user model looks like:
def roles?(user_roles)
user_roles.each do |role|
return true if role?(role)
end
false
end
def role?(role)
roles.any? { |r| r.name.underscore.to_sym == role }
end

Related

Prevent access on users list on Ruby on Rails

I am currently using Pundit as my authorization gem on my Rails app. Basically, I created this page where in if you are admin (which means if a field has the admin value set up to true) you can view this page and delete some existing user on the database viahttp://localhost:3000/dashboard/users`
I already place this on my menu:
<% if current_user.admin === true %>
<%= link_to "Users List", users_path, class: "dropdown-item #{active_class_white('/dashboard/contacts')}" %>
<% end %>
Which will hide this link if the user is not admin. However, this can be still access if the non-admin visits the url directly.
I've already set up my pundit file via application_pundit.rb file which contains the ff:
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
end
Now I don't know what to do how to prevent other users that doesn't have the admin access to view this page and be able to delete any users from the database.
Please help.
UPDATE: I created a user_policy.rb and place the ff codes:
class UserPolicy < ApplicationPolicy
def index?
user.admin?
end
def destroy?
user.admin?
end
end
But this did not work as a non-admin can still access the page. What else should I do?
In user_policy:
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def index?
user.admin?
end
end
In the context of Pundit, user is current_user so this will evalue to true if user is an admin (and to false otherwise).
And in your controller:
def index
#users = policy_scope(User)
authorize User
end

Rails Pundit ActiveAdmin: page isn’t redirecting properly

I installed Activeadmin and Pundit gems.
Added 'include Pundit' in application_controller.rb.
Defined package_policy.rb
class PackagePolicy < ApplicationPolicy
def update?
user.admin?
end
end
application_policy.rb:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
false
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
end
And than I get
page isn’t redirecting properly Firefox has detected that the server is redirecting the request for this address in a way that will never complete
in my browser. Maybe, it is infinity loop or something like it.
I had some different configures of package_policy.rb,
but after added application_policy.rb - the result is always error in browser after trying to log in to Activeadmin panel.
I allowed all actions for all methods in my ApplicationPolicy.
And after I created new policies with needed permissions for my resources.
In ApplicationPolicy:
...
def index?
true
end
def show?
true
end
def create?
true
end
def new?
create?
end
def update?
true
end
def edit?
update?
end
def destroy?
true
end
...
In any other policy, for example:
...
def index?
user.admin?
end
def show?
user.admin?
end
def create?
user.admin?
end
def new?
create?
end
def update?
user.admin?
end
def edit?
update?
end
def destroy?
user.admin?
end
...

Rails - Pundit - how to check for current_admin_user?

I am using Pundit for authorization for my User model.
My goal is to extend this to use my AdminUser model, specifically for my admin namespace.
By default, Pundit checks for a "user" or "current_user". How can I change this to check for a "admin_user" or "current_admin_user", based on Devise?
policies/admin/admin_policy.rb (Closed system, currently looks for User instead of AdminUser)
class Admin::AdminPolicy
attr_reader :user, :record
def initialize(user, record)
# Must be logged in
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
policies/admin/home_policy.rb (Example sub-policy of the Admin namespace)
class Admin::HomePolicy < Admin::AdminPolicy
def index?
user.present?
end
end
I think you need to define the method pundit_user on your controllers to customize it https://github.com/varvet/pundit#customize-pundit-user
def pundit_user
current_admin_user
end

Ruby - Rails 4 - Pundit - Policy and authorization error for a route #index_fr?

Sorry, I didn't see another place to ask a question about Pundit... Thank you for your help.
I am working on a Ruby on rails API and I would like to create an url (.../api/v1/attractions/fr) list some information about one of my models. But I've got this error message from Pundit :
Pundit::AuthorizationNotPerformedError at /api/v1/attractions/fr
Api::V1::AttractionsController
and this error for verify_authorized in the lib/pundit.rb file
def verify_authorized
raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized?
end
This is my configuration :
# app/config/routes.rb
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :lines, only: [ :index, :show ] do
collection do
get '/fr', to: 'attractions#index_fr'
end
end
end
end
# app/controllers/api/v1/attractions_controller.rb
class Api::V1::AttractionsController < Api::V1::BaseController
skip_before_action :authenticate_user!
def index
#attractions = policy_scope(Attraction)
#attractions = Attraction.all
end
def index_fr
#attractions = policy_scope(Attraction)
#attractions = Attraction.all
end
end
# app/policies/application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def index_fr?
false
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
end
end
Try adding before_filter :skip_authorization to your api controller.
However the pundit verify_authorized method should only be called if you've added it as an after_action.

Pundit - Policies are not recognised

I am implementing pundit and wish to restrict the user#edit and user#update actions to only the current_user
def edit
#user = current_user
authorize(#user)
end
def update
#user = current_user
authorise(#user)
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to edit_user_path
else
render 'edit'
end
end
The following is my attempted policy which (a) does not work and (b) is illogical.
class UserPolicy
attr_reader :user, :user
def initialise(user, user)
#user = user
end
def update?
true
end
alias_method :edit?, :update?
end
I have now updated my UserPolicy as per below. I have set the actions to false for testing as everything was being authorised:
class UserPolicy < ApplicationPolicy
def new?
create?
end
def create?
false
end
def edit?
update?
end
def update?
false
#user.id == record.id
end
end
However my policies are not recognised. Upon further reading I added the following to my ApplicationController:
after_filter :verify_authorized, except: :index
after_filter :verify_policy_scoped, only: :index
When I now navigate to my user#edit action I receive:
Pundit::AuthorizationNotPerformedError
First, make sure you have...
your-app/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
end
your-app/app/policies/application_policy.rb with default permissions for common actions.
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
Then, in your UserPolicy
your-app/app/policies/section_policy.rb
class UserPolicy < ApplicationPolicy
def edit?
user.id == record.id
end
def update?
edit?
end
end
So, by default, user will be your current user and record will be the #user defined on edit and update actions.
You don't need to call authorize method explicitly. Pundit knows what to do with your #user attribute. So, your controller should be:
def edit
user
end
def update
if user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to edit_user_path
else
render 'edit'
end
end
private
def user
#user ||= User.find(params[:id])
end
you must know if you don't have a current_user method, yo will need to define a pundit_user in your application controller.

Resources