I have having trouble allowing non-registered/non-logged in users to view the index and show pages for a blog section. I am using Pundit for authorization and realize that at the moment I have my policies set to not allow non-users to view any part of the blog section, but I have no idea how to work around that to have no policy for the index and show page.
My goal is to have the following:
Allow Admin and Editors to view, create, edit, and delete blogs
This portion works pefect
Allow registered users to view blogs
This portion works perfect
Allow non-registered/non-logged in users to view blogs
This part does not work
When I try to view the index page as a non-registered/non-logged in user, I will get an access denied flash message that comes out of my application controller, which is doing what it is supposed to be doing given the current policies.
So my question is: How do I modify my policies to allow non-registered/non-logged in users to view the index and show pages only?
Application Controller
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
before_filter :configure_permitted_parameters, if: :devise_controller?
private
def user_not_authorized(exception)
flash[:danger] = "Access denied. You are not authorized to view that page."
redirect_to (request.referrer || root_path)
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:username, :email, :password, :remember_me) }
devise_parameter_sanitizer.permit(:account_update) {|u| u.permit(:username, :email, :password, :password_confirmation, :current_password)}
end
end
Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
@user = user
@record = record
end
def index?
true
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
Post Policy
class PostPolicy < ApplicationPolicy
attr_reader :post
class Scope < Scope
def resolve
if user&.admin?&.editor?&.user?
scope.all
else user != admin? || editor? || user?
scope
end
end
end
def permitted_attributes
if user.admin? || user.editor?
[:title, :body, :image, :permalink, :description, :tag_list, :username]
else
[:title, :body, :image, :username]
end
end
def index?
true
end
def show?
true
end
def new?
user.admin? || user.editor?
end
def create?
user.admin? || user.editor?
end
def update?
user.admin? || user.editor?
end
def destroy?
user.admin? || user.editor?
end
end
Post Controller
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
after_action :verify_authorized, only: [:destroy]
def index
@meta_title = "Blog"
@meta_description = "page description here"
@posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
end
def show
@meta_title = @post.title
@meta_description = @post.description
end
def new
@meta_title = "Add New Blog"
@meta_description ="Add a new blog."
@post = Post.new
authorize @post
end
def edit
@meta_title = "Edit Blog"
@meta_description ="Edit an existing blog."
authorize @post
end
def create
@post = Post.new
@post.update_attributes(permitted_attributes(@post))
@post.user = current_user if user_signed_in?
authorize @post
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new
end
end
def update
@post = Post.find(params[:id])
if @post.update_attributes(permitted_attributes(@post))
authorize @post
redirect_to @post, notice: 'Post was successfully updated.'
else
render :edit
end
end
def destroy
if @post.present?
@post.destroy
authorize @post
else
skip_authorization
end
redirect_to posts_url, notice: 'Post was successfully deleted.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow the white list through.
def post_params
params.require(:post).permit(policy(@post).permitted_attributes)
end
end
I've seen a similar question asked Pundit policy_scoper error, but the solution suggested there does not seem to work in my case.
After much frustration, I was finally able to solve the issue. Big thanks go out to @Scott for helping get the controller and testing set up as they should be, and nearly getting the policies working.
Turns out that the raise Pundit::NotAuthorizedError, "must be logged in" unless user
in the initializer
section of the Application Policy
was not allowing non-logged-in users from accessing the index page (just like it's supposed to when you want a closed system...). Since my application is open for anyone to view in the index and show pages of the blog, I needed to remove that line.
Once removed the application would then throw a undefined method admin?' for nil:NilClass
for non-logged-in users trying to access the blog index page. This was solved by using the correct conventions of identifying users in Post Policy
. For each def in the policy I had user.admin? || user.editor?
. That needed to be changed to user&.admin? || user&.editor?
.
The code ended up as follows:
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
Post Policy
class PostPolicy < ApplicationPolicy
attr_reader :post
class Scope < Scope
def resolve
if user&.admin? || user&.editor?
scope.all
else
end
end
end
def permitted_attributes
if user.admin? || user.editor?
[:title, :body, :image, :permalink, :description, :tag_list, :username]
else
[:title, :body, :image, :username]
end
end
def index?
true
end
def show?
true
end
def new?
admin_or_editor
end
def create?
admin_or_editor
end
def update?
admin_or_editor
end
def destroy?
admin_or_editor
end
private
def admin_or_editor
user&.admin? || user&.editor?
end
end
Post Controller
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
after_action :verify_authorized, except: [:index, :show]
def index
@meta_title = "Blog"
@meta_description = "blog description"
@posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
end
def show
@meta_title = @post.title
@meta_description = @post.description
end
def new
@meta_title = "Add New Blog"
@meta_description ="Add a new blog."
@post = Post.new
authorize @post
end
def edit
@meta_title = "Edit Blog"
@meta_description ="Edit an existing blog."
authorize @post
end
def create
@post = Post.new
@post.update_attributes(permitted_attributes(@post))
@post.user = current_user if user_signed_in?
authorize @post
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new
end
end
def update
@post = Post.find(params[:id])
authorize @post
if @post.update_attributes(permitted_attributes(@post))
redirect_to @post, notice: 'Post was successfully updated.'
else
render :edit
end
end
def destroy
if @post.present?
@post.destroy
authorize @post
else
skip_authorization
end
redirect_to posts_url, notice: 'Post was successfully deleted.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow the white list through.
def post_params
params.require(:post).permit(policy(@post).permitted_attributes)
end
end