Authorization dengan Pundit untuk Nested Controller dan User Turunan pada Rails
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.
1source 'https://rubygems.org'2git_source(:github) { |repo| "https://github.com/#{repo}.git" }34# ...56gem 'pundit', '~> 2.1'78# ...
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.
1class ApplicationController < ActionController::Base2 include Pundit3end
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/
...
...
1class AuthorsController < ApplicationController2 protect_from_forgery prepend: true, with: :exception3 before_action :authenticate_author!4 layout "application_author"56 def pundit_user7 current_author8 end910 protected1112 def after_sign_in_path_for(_resource)13 authors_root_path14 end15end
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/
...
...
1class AuthorPolicy < ApplicationPolicy2end
1class Author::ArticlePolicy < AuthorPolicy2 def edit?3 record.user_id == user.id4 end5end
Dapat pula seperti ini.
1class Author::ArticlePolicy < AuthorPolicy2 def edit?3 user.present? && user == record.author4 end5end
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.
1class Author::ArticlePolicy < AuthorPolicy2 def edit?3 user.present? && user == article.author4 end56 private78 def article9 record10 end11end
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.
1class Author::ArticlePolicy < AuthorPolicy2 def update?3 user.present? && user == article.author4 end56 def edit?7 update?8 end910 def1112 private1314 def article15 record16 end17end
Authorize controller
Nah, kita telah mengatur policy untuk action edit, maka kita perlu memberikan authorization pada action edit di articles_controller.
1class Authors::ArticlesController < AuthorsController2 # ...34 def edit5 @article = Article.find(params[:id])6 authorize @article, policy_class: Author::ArticlePolicy7 end89 # ...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),
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.
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
- github.com/varvet/pundit
Diakses tanggal: 2021/04/09