Pundit::NotAuthorizedError / Problem with pundit authorize - ruby-on-rails

I'm trying to update user's adress in a form but i dont understant why i'm not authorize to perform, this is my code :
class AddressesController < ApplicationController
def update
#address = current_user.addresses.last
authorize #address
#address.update!(address_params)
end
private
def address_params
params.require(:address).permit(:first_name, :last_name, :city, :country, :postcode, :phone_number, :street_address, :optional_address, :user_id)
end
end
class AddressPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
def update?
true
end
end
end
and this is the error :
Pundit::NotAuthorizedError in AddressesController#update
not allowed to update? this Address

You've defined the update? method within the nested Scope class, but it's supposed to be defined directly in the policy class.
Instead of this:
class AddressPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
def update?
true
end
end
end
You need to do this:
class AddressPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def update?
true
end
end

Related

Rails - validation inside decorator

I'm struggling with some kind of issue. I have a rails model (mongoid).
class User
include Mongoid::Document
include ActiveModel::SecurePassword
validate :password_presence,
:password_confirmation_match,
:email_presence,
field :email
field :password_digest
def password_presence
end
def email_presence
end
def password_confirmation_match
end
end
My goal is to call validations depends on which decorator I will use. Let's say I've got two decorators:
class PasswordDecorator < Draper::Decorator
def initialize(user)
#user = user
end
end
def RegistraionDecorator < Draper::Decorator
def initialize(user)
#user = user
end
end
So now when I create/save/update my user object inside RegistraionDecorator I would like to perform all validation methods.
RegistraionDecorator.new(User.new(attrbiutes))
But when I will do it inside PasswordDecorator I want to call for example only password_presence method.
PasswordDecorator.new(User.first)
When I move validations to decorator it won't work cuz its different class than my model.
How can I achieve that?
Try to use a Form Object pattern instead.
Here is an example (from a real project) of how it could be done with reform.
class PromocodesController < ApplicationController
def new
#form = PromocodeForm.new(Promocode.new)
end
def create
#form = PromocodeForm.new(Promocode.new)
if #form.validate(promo_params)
Promocode.create!(promo_params)
redirect_to promocodes_path
else
render :edit
end
end
private
def promo_params
params.require(:promocode).
permit(:token, :promo_type, :expires_at, :usage_limit, :reusable)
end
end
class PromocodeForm < Reform::Form
model :promocode
property :token
property :promo_type
property :expires_at
property :usage_limit
property :reusable
validates_presence_of :token, :promo_type, :expires_at, :usage_limit, :reusable
validates_uniqueness_of :token
validates :usage_limit, numericality: { greater_or_equal_to: -1 }
validates :promo_type, inclusion: { in: Promocode::TYPES }
end
Bonus: The model does not trigger validations and much easy to use in tests.

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.

How to retrieve nested model data from another model? : undefined method error

So the basis of my code so far is:
a customer has_one calendar
a calendar belongs_to a customer
a calendar has_many events
an event belongs_to a calendar
I am trying to, when creating a new event, specify the customer and calendar it belongs to but it throws error "undefined method `Calendar'":
class EventsController < ApplicationController
def new
#event = Event.new
#currentcalendar = current_customer.calendar # this is where it is failing
end
def create
if #event = #currentcalendar.build.event(event_params)
redirect_to '/main'
else
redirect_to '/compose'
end
end
private
def event_params
params.require(:event).permit(:calendar_id, :name, :starts_at, :ends_at)
end
end
this is my current_customer method within application_controller:
def current_customer
if (customer_id = session[:customer_id])
#current_customer ||= Customer.find_by(id: customer_id)
elsif (customer_id = cookies.signed[:customer_id])
customer = Customer.find_by(id: customer_id)
if customer && customer.authenticated?(cookies[:remember_token])
session[:customer_id] = customer.id #log in
#current_customer = customer
end
end
end
Here are the related controller files. Customer:
class CustomersController < ApplicationController
def new
#customer = Customer.new
#businesses = Business.all
#calendar = Calendar.new
end
def create
#customer = Customer.create(customer_params)
#calendar = #customer.build_calendar
#customer.save!
session[:customer_id] = #customer.id
redirect_to '/'
rescue ActiveRecord::RecordInvalid => ex
render action: 'new', alert: ex.message
end
private
def customer_params
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password, :business_id)
end
Calendar:
class CalendarsController < ApplicationController
def new
#calendar = Calendar.new(calendar_params)
end
def create
#calendar = Calendar.new(calendar_params)
end
private
def calendar_params
params.require(:customer_id)
end
end
I'm very new to Ruby/ Rails and so can't figure this out by myself. Is this problem occurring because I have wrongly created my calendar? I wanted it to be created when its user is created, which works, but I just don't know how to get to the calendar and user within the events controller.
Thanks for your help!
EDIT: these are the model classes.
customer:
class Customer < ActiveRecord::Base
belongs_to :business
has_one :calendar
has_secure_password
attr_accessor :remember_token
#remembers a user in the database for use in persistent sessions
def remember
self.remember_token = Customer.new_token
update_attribute(:remember_digest, Customer.digest(remember_token))
end
def Customer.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def forget
update_attribute(:remember_digest, nil)
end
def Customer.new_token
SecureRandom.urlsafe_base64
end
#returns true if the given token matches the digest
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
calendar:
class Calendar < ActiveRecord::Base
belongs_to :customer
has_many :events
end
event:
class Event < ActiveRecord::Base
belongs_to :calendar
end
Your current_customer can be nil at times. To avoid this you can add a before_filter callback that checks if there is a customer that is logged in or not.
In your application_controller create a method called customer_found?
def customer_found?
current_customer.present?
end
Change your events controller to
class EventsController < ApplicationController
before_filter :customer_found?
before_filter :prepare_calendar, only: [:new, :create]
def new
#event = Event.new
end
def create
if #event = #current_calendar.build.event(event_params)
redirect_to '/main'
else
redirect_to '/compose'
end
end
private
def prepare_calendar
#current_calendar = current_customer.calendar
end
def event_params
params.require(:event).permit(:calendar_id, :name, :starts_at, :ends_at)
end
end
Since you did not assign your #current_calendar in your create method then you are gonna get undefined method build for nil class. You need to initialize the variable since it can not get it from the new method. Each action has its own independent variables so make sure to prepare all necessary variables before using them.

How to make pundit policies more DRY?

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

Pundit- Index Method for Admin and Users

So, I'm trying to use the gem pundit. I'm just trying to figure out how to have an index view for users and admins. I want to render all results for an admin and only related posts for a user. I've googled and searched on github, but I'm not find any luck. What do I have to put in my policy and controller?
original code
class PostsPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#post = model
end
def index?
#current_user.admin?
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = Post.order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
second update
class PostsPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
third update
class PostPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
def index?
user.admin? || user.posts.count > 0
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
** final update with working code **
class PostPolicy
attr_reader :user, :model
def initialize(user, model)
#user = user
#post = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
def index?
user.admin? || user.posts.count
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize Post
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
What you're looking for is a Scope:
class PostsPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
end
Then in your controller
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
Edit
As a side note, authorize User will probably not serve you well in the long run. You're essentially creating an index policy that would need to serve every index. If you want to to authorize visibility to the index page you can still do something like this in your policy:
def index?
user.admin? || user.posts.count > 0
end
Assuming that relationship is set up, then you would call authorize Post in your index controller before your policy_scope.

Resources