BanditHijo.dev

Authorization dengan Pundit untuk Nested Controller dan User Turunan pada Rails

Created at: 2021-04-09
Author by: BanditHijo

Prerequisite

ruby 3.0.1 rails 6.1.3.1

Target

Membatasi antar Author untuk mengedit dan menghapus Article yang bukan miliknya.

Untuk feature authorization tersebut, kita akan gunakan Pundit gem.

Sekenario

Author merupakan turunan dari User dengan type β€œAuthor”.

Kita ingin membuat feature administrasi untuk Author yang menampilkan semua daftar Article dan Semua Author.

Hanya Author pemilik Article yang dapat mengedit/menghapus Article yang ia miliki.

Eksekusi

Pasang Pundit Gem

Pasang Pundit pada Gemfile.

Gemfile
1source 'https://rubygems.org'
2git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
4# ...
5
6gem 'pundit', '~> 2.1'
7
8# ...

Install.

$ bundle install

Generate pundit:install

Jalankan generator untuk membuat konfigurasi awal.

$ rails g pundit:install

Generator ini akan membuatkan direktori app/policies dan juga file bernama application_policy.rb.

πŸ“‚ app/
β”‚ πŸ“ assets/
β”‚ πŸ“ channels/
β”‚ πŸ“ controllers/
β”‚ πŸ“ helpers/
β”‚ πŸ“ javascript/
β”‚ πŸ“ jobs/
β”‚ πŸ“ mailers/
β”‚ πŸ“ models/
β”‚ πŸ“‚ policies/ πŸ‘ˆοΈ
β”‚ β”” πŸ“„ application_policy.rb πŸ‘ˆοΈ
β”” πŸ“ views
πŸ“ bin
πŸ“ config
πŸ“ db
πŸ“ lib
...
...

Include Pundit

Saya akan mengincludekan Pundit pada application_controller agar setiap controller turunan dapat menggunakan Pundit.

app/controllers/application_controller.rb
1class ApplicationController < ActionController::Base
2 include Pundit
3end

Define pundit_user

Pundit menyediakan objek current_user sebagai instansiasi terhadap user yang sudah melakukan authentication.

Namun, karena kita menggunakan user type berupa Author yang merupakan turunan dari User, kita perlu memodifikasi method pundit_user.

Kita letakkan pada controller.

Saya memiliki authors_controller yang merupakan induk dari semua controller yang ada di bawahnya.

πŸ“‚ app/
β”‚ πŸ“ assets/
β”‚ πŸ“ channels/
β”‚ πŸ“‚ controllers/
β”‚ β”‚ πŸ“ admins/
β”‚ β”‚ πŸ“‚ authors/
β”‚ β”‚ β”‚ πŸ“„ articles_controller.rb
β”‚ β”‚ β”‚ πŸ“„ confirmations_controller.rb
β”‚ β”‚ β”‚ πŸ“„ dashboard_controller.rb
β”‚ β”‚ β”‚ πŸ“„ omniauth_callbacks_controller.rb
β”‚ β”‚ β”‚ πŸ“„ passwords_controller.rb
β”‚ β”‚ β”‚ πŸ“„ registrations_controller.rb
β”‚ β”‚ β”‚ πŸ“„ sessions_controller.rb
β”‚ β”‚ β”” πŸ“„ unlocks_controller.rb
β”‚ β”‚ πŸ“ concerns/
β”‚ β”‚ πŸ“ public/
β”‚ β”‚ πŸ“„ admins_controller.rb
β”‚ β”‚ πŸ“„ application_controller.rb
β”‚ β”‚ πŸ“„ articles_controller.rb
β”‚ β”” πŸ“„ authors_controller.rb πŸ‘ˆοΈ
β”‚ πŸ“ helpers/
β”‚ πŸ“ javascript/
β”‚ πŸ“ jobs/
β”‚ πŸ“ mailers/
β”‚ πŸ“ models/
β”‚ πŸ“‚ policies/
β”‚ β”” πŸ“„ application_policy.rb
β”” πŸ“ views/
πŸ“ bin/
πŸ“ config/
πŸ“ db/
πŸ“ lib/
...
...
/app/controller/authors_controller.rb
1class AuthorsController < ApplicationController
2 protect_from_forgery prepend: true, with: :exception
3 before_action :authenticate_author!
4 layout "application_author"
5
6 def pundit_user
7 current_author
8 end
9
10 protected
11
12 def after_sign_in_path_for(_resource)
13 authors_root_path
14 end
15end

Baris ke 6-8, saya mendefinisikan pundit_user sebagai current_author.

Buat policy untuk Article

Karena yang ingin kita batasi adalah Article agar hanya Author si pemilik Article saja yang dapat memodifikasinya.

Struktur direktori dan file dari policy ini mengikuti dari controller namun menggunakan singular.

πŸ“‚ app/
β”‚ πŸ“ assets/
β”‚ πŸ“ channels/
β”‚ πŸ“ controllers/
β”‚ πŸ“ helpers/
β”‚ πŸ“ javascript/
β”‚ πŸ“ jobs/
β”‚ πŸ“ mailers/
β”‚ πŸ“ models/
β”‚ πŸ“‚ policies/
β”‚ β”‚ πŸ“‚ author_policy/ πŸ‘ˆοΈ
β”‚ β”‚ β”” πŸ“„ article_policy.rb πŸ‘ˆοΈ
β”‚ β”‚ πŸ“„ application_policy.rb
β”‚ β”” πŸ“„ author_policy.rb πŸ‘ˆοΈ
β”” πŸ“ views/
πŸ“ bin/
πŸ“ config/
πŸ“ db/
πŸ“ lib/
...
...
app/policies/author_policy.rb
1class AuthorPolicy < ApplicationPolicy
2end
app/policies/author/article_policy.rb
1class Author::ArticlePolicy < AuthorPolicy
2 def edit?
3 record.user_id == user.id
4 end
5end

Dapat pula seperti ini.

app/policies/author/article_policy.rb
1class Author::ArticlePolicy < AuthorPolicy
2 def edit?
3 user.present? && user == record.author
4 end
5end

Misalkan, kita akan membatasi action edit, maka kita definisikan method edit? dengan isinya, apabila user_id dari record sama dengan id dari user yang sedang mengakses, maka diberikan ijin untuk mengedit.

record dapat pula kita buat menjadi method berisi record.

app/policies/author/article_policy.rb
1class Author::ArticlePolicy < AuthorPolicy
2 def edit?
3 user.present? && user == article.author
4 end
5
6 private
7
8 def article
9 record
10 end
11end

Letakkan di dalam private agar penamaan article hanya dapat diakses oleh class Author::ArticlePolicy.

Karena edit, sangat erat dengan update, maka saya akan buat seperti ini.

app/policies/author/article_policy.rb
1class Author::ArticlePolicy < AuthorPolicy
2 def update?
3 user.present? && user == article.author
4 end
5
6 def edit?
7 update?
8 end
9
10 def
11
12 private
13
14 def article
15 record
16 end
17end

Authorize controller

Nah, kita telah mengatur policy untuk action edit, maka kita perlu memberikan authorization pada action edit di articles_controller.

app/controllers/authors/articles_controller.rb
1class Authors::ArticlesController < AuthorsController
2 # ...
3
4 def edit
5 @article = Article.find(params[:id])
6 authorize @article, policy_class: Author::ArticlePolicy
7 end
8
9 # ...
10end

Baris ke-6 adalah pemberian authorization pada action edit.

Parameter policy_class ini sebenarnya adalah cara manual untuk mengarahkan file policy.

Saya menggunakannya hanya sebagai contoh siapa tahu kita mendapatkan kasus-kasus khusus, seperti nama Object dengan nama Controller atau Policy tidak sama.

Views Template

Selanjutnya, cara membatasi button atau link yang hanya dikhususkan untuk Author yang memiliki Article.

Misalnya, button atau link untuk Edit atau Delete.

Sebelum menggunakan Pundit Policy, saya biasa menggunakan cara seperti ini (baris ke-1),

app/views/authors/articles/show.html.erb
1<% if @article.user_id == current_author.id %>
2 <%= link_to 'Edit', edit_authors_article_path(@news), class: 'btn btn-info' %>
3 <%= link_to 'Delete', authors_article_path(@article), method: :delete, data: {confirm: "Are you sure, you want to delete the article?"}, class: 'btn btn-danger' %>
4<% end %>

Setelah menggunakan Pundit, kita dapat memanfaatkan policy yang ada.

app/views/authors/articles/show.html.erb
1<% if policy([Authors, @article]).edit? %>
2 <%= link_to 'Edit', edit_authors_article_path(@news), class: 'btn btn-info' %>
3 <%= link_to 'Delete', authors_article_path(@article), method: :delete, data: {confirm: "Are you sure, you want to delete the article?"}, class: 'btn btn-danger' %>
4<% end %>

Saya menggunakan [Authors, @article], karena articles_controller merupakan controller bertingkat (nested controller) dari Authors.

1policy([Authors, @article]).edit?

Kalau tidak bertingkat, dapat langsung memanggil objek modelnya saja.

1policy(Article).edit?

Selesai.

Pesan Penulis

Sepertinya, segini dulu yang dapat saya tuliskan.

Selanjutnya, saya serahkan kepada imajinasi dan kreatifitas teman-teman. Hehe.

Mudah-mudahan dapat bermanfaat.

Terima kasih.

(^_^)

Referensi

  1. github.com/varvet/pundit
    Diakses tanggal: 2021/04/09