Nested Resources For Pundit Policy - ruby-on-rails

I'm still trying to wrap my head around Pundit policies. I think I'm close but I've wasted too much time trying to figure this out. My Posts policy works great, but trying to authorize comments, I am getting undefined errors...
comments_controller.rb
class CommentsController < ApplicationController
before_action :find_comment, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:comment))
#comment.user_id = current_user.id if current_user
authorize #comment
#comment.save
if #comment.save
redirect_to post_path(#post)
else
render 'new'
end
end
def edit
authorize #comment
end
def update
authorize #comment
if #comment.update(params[:comment].permit(:comment))
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
authorize #comment
#comment.destroy
redirect_to post_path(#post)
end
private
def find_comment
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
end
comment_policy.rb
class CommentPolicy < ApplicationPolicy
def owned
comment.user_id == user.id
end
def create?
comment.user_id = user.id
new?
end
def new?
true
end
def update?
edit?
end
def edit?
owned
end
def destroy?
owned
end
end
The formatting and indenting is a bit off... thats not how I code I swear
class ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#post = post
end
def index?
false
end
def show?
scope.where(:id => post.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, post.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end

You initialized your resource in ApplicationPolicy as #post. And since your CommentPolicy inherits from ApplicationPolicy and uses its initialization, it only has access to #post. Best option is to keep it as record:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#record = record
end
## code omitted
end
class CommentPolicy < ApplicationPolicy
def owned
record.user_id == user.id
end
## code omitted
end
Basically you can call it anything you want but record makes more sense as it will be used in different policy subclasses.

Related

Can't delete using pundit as user.admin or record.user. How can I get them to delete?

I'm using the Pundit gem for the user authorizations in my Rails project. The Edit function works as I expected, just user admin and whoever created the review is able to update it. However, I can't delete them with the pundit set up.
Here's my Reviews Controller:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
def index
#reviews = Review.all
end
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to review_path(#review), :alert => "Awesome! Here's your small review!"
else
error_messages(#review)
render 'new'
end
end
def show
end
def edit
authorize #review
end
def update
if #review.update(review_params)
redirect_to review_path(#review), :alert => "That's a good update!"
else
error_messages(#review)
render 'edit'
end
end
def destroy
authorize #review
#review.destroy
redirect_to reviews_path, :alert => "We'll miss that review."
end
private
def set_review
#review = Review.find_by(id: params[:id])
end
def review_params
params.require(:review).permit(:title, :content)
end
end
The Application Policy looks like this:
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 create?
new?
end
def update?
false
end
def edit?
update?
end
def destroy?
destroy?
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
And here's my customized Review Policy file:
class ReviewPolicy < ApplicationPolicy
def edit?
user.admin? || record.user == user
end
def destroy?
user.admin? || record.user == user
end
end
In case you're wondering, I created the user_not_authorized helper method. My Application Controller looks like this.
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protect_from_forgery with: :exception
helper_method :current_user
add_flash_types :info, :error, :warning
def logged_in?
!!current_user
end
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
def error_messages(obj)
obj.errors.messages.each do |k,v|
flash[:alert] = "#{k.to_s} #{v.first.to_s}"
end
end
private
def user_not_authorized
flash[:alert] = "Ooops sorry. You don't have access to this."
redirect_to (request.referrer || root_path)
end
end
Hope my message is clear enough and would appreciate any help. Remember, '''user.admin? || record.user == user''' works for editing but the user admins and the review creators can't delete the reviews when applying the same methods.
Please let me know if any question.

Pundit policy_scope error: undefined method `admin?' for nil:NilClass

Running into something I don't understand with Pundit,
Using Rails 4.2.5.1, Pundit 1.1.0 with Devise for authentication.
I'm trying to use a policy scope for the BlogController#Index action.
If user is admin, display all posts (drafts, published)
If user is standard, display posts marked published only
If no user / user not logged in, display posts marked published only
Getting an error:
undefined method `admin?' for nil:NilClass
Live shell reveals:
>> user
=> nil
# ApplicationController
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
private
def user_not_authorized
flash[:error] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
end
end
# BlogController
# == Schema Information
#
# Table name: blogs
#
# id :integer not null, primary key
# title :string default(""), not null
# body :text default(""), not null
# published :boolean default("false"), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
after_action :verify_authorized, except: [:index, :show]
after_action :verify_policy_scoped, only: [:index]
def index
#blogs = policy_scope(Blog)
authorize #blog
end
def show
end
def new
#blog = Blog.new
authorize #blog
end
def edit
authorize #blog
end
def create
#blog = Blog.new(blog_params)
#blog.user = current_user if user_signed_in?
authorize #blog
if #blog.save
redirect_to #blog, notice: "Blog post created."
else
render :new
end
end
def update
authorize #blog
if #blog.update(blog_params)
redirect_to #blog, notice: "Blog updated."
else
render :edit
end
end
def destroy
authorize #blog
#blog.destroy
redirect_to blogs_url, notice: "Blog post deleted."
end
private
def set_blog
#blog = Blog.friendly.find(params[:id])
end
def blog_params
params.require(:blog).permit(*policy(#blog|| Blog).permitted_attributes)
end
end
# 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
end
# Blog Policy
class BlogPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where(published: true)
end
end
end
def new?
user.admin?
end
def index?
true
end
def update?
user.admin?
end
def create?
user.admin?
end
def destroy?
user.admin?
end
def permitted_attributes
if user.admin?
[:title, :body]
end
end
end
In the Pundit BlogPolicy scope I've created:
class Scope < Scope
def resolve
if user.admin?
scope.order('id DESC')
else
scope.where('published: true')
end
end
end
If I log in as an admin user it works fine.
I'm able to view all blog posts.
If I log in as a standard user it works.
Standard user sees blog posts that are marked published.
If I'm not logged in where user is nil I get an error:
NoMethodError at /blog
undefined method `admin?' for nil:NilClass
I can add another clause elsif user.nil? before user.admin? or a case when statement but I thought if the user is not an admin it should just display what is in the else block?
# This seems wrong?
class Scope < Scope
def resolve
if user.nil?
scope.where('published: true')
elsif user.admin?
scope.all
else
scope.where('published: true')
end
end
end
Any pointers much appreciated
You can use try:
if user.try(:admin?)
# do something
end
http://api.rubyonrails.org/v4.2.5/classes/Object.html#method-i-try
This happens because there is no user when you are not logged in. (Probably to user variable nil value is assigned somewhere, so you are trying to call admin? method on nil object)
If you use ruby 2.3.0 or newer you had better use safe navigation
if user&.admin?
scope.order('id DESC')
else
scope.where('published: true')
end
If you user other ruby version
if user.try(:admin?)
scope.order(id: :desc)
else
scope.where(published: true)
end

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.

Pundit rescue_from not redirecting or displaying alert message when non-user accesses posts/new

I'm trying to add authorization to my rails app and want to redirect a non-user to root_url when they try to access posts/new, using rescue_from. However, there is no redirect to root or error message being displayed and I'm not sure why.
This is my application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
rescue_from Pundit::NotAuthorizedError do |exception|
redirect_to root_url, alert: exception.message
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :name
end
end
This is application_policy.rb
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?
user.present?
end
def new?
create?
end
def update?
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
record.class
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
This is post_policy.rb
class PostPolicy < ApplicationPolicy
def new
#post = Post.new
authorize #post
end
end
This is posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#ApplicationController::Find
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(params.require(:post).permit(:title, :body))
if #post.save
flash[:notice] = "Post was saved."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
render :new
end
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update_attributes(params.require(:post).permit(:title, :body))
flash[:notice] = "Post was updated."
redirect_to #post
else
flash[:error] = "There was an error saving the post. Please try again."
end
end
end
The issue was that I defined another new method in the post_policy.rb file, overwriting the new method in application_policy.rb. I also didn't include authorize #post in the new method in the posts_controller.rb file.

undefined method `admin?' for #

Good afternoon,
I am attempting to fix I am having with my rails application.
Heres a direct image to the issue:
http://oi62.tinypic.com/2z6v5w2.jpg
here is my topic_policy.rb
class TopicPolicy < ApplicationPolicy
def index?
true
end
def create?
user.present? && user.admin?
end
def update?
create?
end
end
Here is my application_policy.rb
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?
user.present?
end
def new?
create?
end
def update?
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
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 here is the topics_controller.rb
class TopicsController < ApplicationController
def index
#topics = Topic.all
authorize #topics
end
def new
#topic = Topic.new
authorize #topic
end
def show
#topic = Topic.find(params[:id])
authorize #topic
end
def edit
#topic = Topic.find(params[:id])
authorize #topic
end
def create
#topic = Topic.new(params.require(:topic).permit(:name, :description, :public))
authorize #topic
if #topic.save
redirect_to #topic, notice: "Topic was saved successfully."
else
flash[:error] = "Error creating topic. Please try again."
render :new
end
end
def update
#topic = Topic.find(params[:id])
authorize #topic
if #topic.update_attributes(params.require(:topic).permit(:name, :description, :public))
redirect_to #topic
else
flash[:error] = "Error saving topic. Please try again"
render :edit
end
end
end
I am just attempting to get it to view a web page through my rails app, but I am stuck.
I was able to resolve this issue by making the admin method public, it was set to private.

Resources