[RUBY] # 16 policy setting to build bulletin board API with authentication authorization in Rails 6

Building a bulletin board API with authentication authorization with Rails 6 # 15 pundit introduction

Edit post_policy

First, change spec / spec_helper.rb as follows. As stated in the pundit formula, you can use the rspec method for pundit by adding the following.

spec/spec_helper.rb


 RSpec.configure do |config|
...

+  require "pundit/rspec"
 end

Next, we will incorporate the test into spec / policies / post_policy_spec.rb.

spec/policies/post_policy_spec.rb


# frozen_string_literal: true

require "rails_helper"

RSpec.describe PostPolicy, type: :policy do
  let(:user) { create(:user) }
  let(:post) { create(:post) }

  subject { described_class }

  permissions :index?, :show? do
    it "Allowed when not logged in" do
      expect(subject).to permit(nil, post)
    end
  end

  permissions :create? do
    it "Not allowed when not logged in" do
      expect(subject).not_to permit(nil, post)
    end
    it "Allow when logged in" do
      expect(subject).to permit(user, post)
    end
  end

  permissions :update?, :destroy? do
    it "Not allowed when not logged in" do
      expect(subject).not_to permit(nil, post)
    end
    it "Not allowed when logged in but another user" do
      expect(subject).not_to permit(user, post)
    end
    it "Allowed when logged in and the same user" do
      post.user = user
      expect(subject).to permit(user, post)
    end
  end
end

I think you can read it somehow, but just in case, please explain it.

  permissions :index?, :show? do
    it "Allowed when not logged in" do
      expect(subject).to permit(nil, post)
    end
  end

Since index? And show? have the same conditions, they are tested together. permit (nil, post) specifies the login user model in the first argument and the target model in the second argument. Then, it tests whether the user of the first argument has the authority of index? And show? Of the object of the second argument.

  permissions :update?, :destroy? do
...
    it "Not allowed when logged in but another user" do
      expect(subject).not_to permit(user, post)
    end
    it "Allowed when logged in and the same user" do
      post.user = user
      expect(subject).to permit(user, post)
    end
  end

I'm still looking at not_to, but it's a test of not being allowed. And the final test passes by matching the owner user of the post.

Modify request spec

Let's move rspec once. Then spec / requests / v1 / posts_request_spec.rb will be quite moss.

The cause is the same as above, because if #update or #destory is not the login of the user to which it belongs, it will be 403. However, the ʻauthorized_user_headers helper used in posts_request_spec.rbdoescreate (: user)` internally, so the logged-in user and post user cannot be matched.

Therefore, make the following modifications.

spec/support/authorization_spec_helper.rb


 module AuthorizationSpecHelper
-  def authorized_user_headers
-    user = create(:user)
+  def authorized_user_headers(user = nil)
+    user = create(:user) if user.nil?
     post v1_user_session_url, params: { email: user.email, password: "password" }

Now, if you pass it to ʻauthorized_user_headers` with no arguments, a user will be created internally, and if you pass a user as an argument, it will be used.

However, ʻauthorized_user_headers` becomes a little complicated and gets stuck in AbcSize of rubocop, so we will take the following measures.

diff:.rubocop.yml



...
+
+#AbcSize Default 15 is hard, so raise it to 20
+Metrics/AbcSize:
+  Max: 20

Now, it's finally time to fix the request spec.

spec/requests/v1/posts_request_spec.rb


...
     it "Normal response code is returned" do
-      put v1_post_url({ id: update_param[:id] }), params: update_param
+      post = Post.find(update_param[:id])
+      put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user)
       expect(response.status).to eq 200
     end
     it "subject,body returns correctly" do
-      put v1_post_url({ id: update_param[:id] }), params: update_param
+      post = Post.find(update_param[:id])
+      put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user)
       json = JSON.parse(response.body)
       expect(json["post"]["subject"]).to eq("update_subject test")
       expect(json["post"]["body"]).to eq("update_body test")
     end
     it "Errors are returned when the parameter is invalid" do
-      put v1_post_url({ id: update_param[:id] }), params: { subject: "" }
+      post = Post.find(update_param[:id])
+      put v1_post_url({ id: update_param[:id] }), params: { subject: "" }, headers: authorized_user_headers(post.user)
       json = JSON.parse(response.body)
       expect(json.key?("errors")).to be true
     end
@@ -106,13 +109,13 @@ RSpec.describe "V1::Posts", type: :request do
       create(:post)
     end
     it "Normal response code is returned" do
-      delete v1_post_url({ id: delete_post.id })
+      delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user)
       expect(response.status).to eq 200
     end
     it "One less and returns" do
       delete_post
       expect do
-        delete v1_post_url({ id: delete_post.id })
+        delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user)
       end.to change { Post.count }.by(-1)
     end

There is no big change so much. Pass authorization by passing the owner of the post to ʻauthorized_user_headers`.

Creating a matching user match determination method

I will change this a little more intuitively. The process of determining whether it belongs to you is not limited to post, and it seems that it will be diverted to various models in the future.

  def update?
    @record.user == @user
  end
 
  def destroy?
    @record.user == @user
  end

Therefore, create a private method in application_policy.rb to determine if it belongs to you.

app/policies/application_policy.rb


class ApplicationPolicy
...

+  private
+
+  def mine?
+    @record.user == @user
+  end

   #
   # scope
   #
  class Scope
...

I will reflect it in post_policy.

app/policies/post_policy.rb


   def update?
-    @record.user == @user
+    mine?
   end
 
   def destroy?
-    @record.user == @user
+    mine?
   end

It was refreshing. Now, from now on, the action to be executed only when the model related to user owns it is just to create a policy file and place the mine? method. It's super easy.

Continued

Building a bulletin board API with authentication authorization in Rails 6 # 17 Adding administrator privileges [To the serial table of contents]

Recommended Posts

# 16 policy setting to build bulletin board API with authentication authorization in Rails 6
# 8 seed implementation to build bulletin board API with authentication authorization in Rails 6
Introduced # 9 serializer to build bulletin board API with authentication authorization in Rails 6
# 6 show, create implementation to build bulletin board API with authentication authorization in Rails 6
Introduced # 10 devise_token_auth to build a bulletin board API with authentication authorization in Rails 6
Introducing # 15 pundit to build a bulletin board API with authentication authorization in Rails 6
# 7 update, destroy implementation to build bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails # 13 Add authentication header
Build a bulletin board API with authentication authorization in Rails 6 # 5 controller, routes implementation
Build a bulletin board API with authentication authorization in Rails # 17 Add administrator privileges
Build a bulletin board API with authentication authorization in Rails 6 # 14 seed Execution time display
Build a bulletin board API with authentication and authorization with Rails 6 # 1 Environment construction
Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post
Build a bulletin board API with authentication authorization in Rails 6 # 11 User model test and validation added
Build a bulletin board API with authentication authorization with Rails 6 # 2 Introducing git and rubocop
Build a bulletin board API with authentication and authorization with Rails # 18 ・ Implementation of final user controller
Build a bulletin board API with authentication authorization with Rails 6 # 3 RSpec, FactoryBot introduced and post model
Building a bulletin board API with authentication authorization with Rails 6 Validation and test implementation of # 4 post
How to build API with GraphQL and Rails
I tried to make a group function (bulletin board) with Rails
How to build Rails 6 environment with Docker
Try to create a bulletin board in Java
One way to redirect_to with parameters in rails
[Rails] How to build an environment with Docker
[How to insert a video in haml with Rails]
How to query Array in jsonb with Rails + postgres
Build Rails (API) x MySQL x Nuxt.js environment with Docker
Implementation policy to dynamically save and display Timezone in Rails
I was addicted to setting default_url_options with Rails devise introduction
[Rails] Create API to download files with Active Storage [S3]
How to build Rails, Postgres, ElasticSearch development environment with Docker
How to set up a proxy with authentication in Feign
Things to keep in mind when using Sidekiq with Rails
802.1X authentication to the network of Bonding setting in CentOS7
Japaneseize using i18n with Rails
Implement LTI authentication in Rails
API creation with Rails + GraphQL
I implemented Rails API with TDD by RSpec. part2 -user authentication-
What I was addicted to when implementing google authentication with rails
How to get boolean value with jQuery in rails simple form
How to rename a model with foreign key constraints in Rails
How to build Rails + Vue + MySQL environment with Docker [2020/09 latest version]
I implemented Rails API with TDD by RSpec. part3-Action implementation with authentication-
Steps to build a Ruby on Rails development environment with Vagrant