Damir Nurgaliev Damir Nurgaliev - 22 days ago 7
Ruby Question

Authorization settings using pundit gem rails

I'm new at rails so bear with me pls. My problem is so specific. I'm creating a User blog, where they could put any posts. So Users has a blogs, and blogs has posts. So when user create a blog, all posts in his blog should be written by him. Other users can't write not on their blogs.


post_controller.rb


class PostsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_user!, only: [:edit, :update, :destroy]

expose :blog
expose :post

def show
end

def new
end

def edit
end

def create
post.user = current_user
post.save
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end

def update
post.update(post_params)
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end

def destroy
post.destroy
respond_with post, location: user_blog_path(post.blog.user, post.blog)
end

private

def authorize_user!
authorize(post, :authorized?)
end

def post_params
params.require(:post).permit(:title, :content, :user_id, :blog_id)
end
end


Here i'm using pundit to authorize user, when they update or destroy posts (users can update or destroy only their own posts) and it works perfectly.

views/posts/new

.row
.columns
h2 = title("New post")

.row
.medium-5.columns
= simple_form_for post do |f|
= f.error_notification

.form-inputs
= f.input :title
= f.input :content
= f.hidden_field :blog_id, value: blog.id
.form-actions
= f.button :submit


Here i'm using the hidden form to set the blog_id which I take from params. Http link looks like http://localhost:3000/posts/new?blog_id=6. The problem is that each user can copy this link to create the post( and they are not the blog owners).

post_policy.rb

class PostPolicy < ApplicationPolicy
def authorized?
record.user == user
end
end


How should I check the blog's owner before post creating? Maybe I have a wrong way to create posts like this(using hidden form).

Link to create new post

= link_to 'New Post', new_post_path(blog_id: blog.id)

Answer

I hope, it will work for you

application_controller.rb

  class ApplicationController

      include Pundit

      after_action :verify_authorized, except: :index
      after_action :verify_policy_scoped, only: :index

      before_action :authenticate_admin_user!

      helper_method :current_user

      def pundit_user
        current_admin_user
      end

      def current_user
        @current_user ||= User.find(current_admin_user.id)
      end

    end

posts_controller.rb

class PostsController < ApplicationController

  before_action :set_blog

  def new
    authorize(Post)
  end

  def edit
    @post = @blog.posts.find(params[:id])
    authorize(@post)
  end

  def index
    @posts = policy_scope(@blog.posts)
  end

  private

  def set_blog
    @blog = current_user.blogs.find(params[:blog_id])
  end

end

post_policy.rb

class PostPolicy < ApplicationPolicy

  def show?
    true
  end

  def index?
    true
  end

  def new?
    create?
  end

  def create?
    true
  end

  def edit?
    update?
  end

  def update?
    scope_include_object?
  end

  def destroy?
    scope_include_object?
  end

  class Scope < Scope

    def resolve
      scope.joins(:blog).where(blogs: { admin_user_id: user.id })
    end

  end

  def scope_include_object?
    scope.where(id: record.id).exists?
  end

end

routes.rb

Rails.application.routes.draw do
  devise_for :admin_users

  resources :blogs do
    resources :posts
  end
end