Otorisasi dengan Pundit
() translation by (you can also view the original English article)
Pundit adalah sebuah alat yang mengizinkanmu melarang bagian tertentu dari aplikasi Rails mu untuk otorisasi pengguna. Caranya dengan menyediakan beberapa pembantu padamu.
Dalam tutorial ini, kamu akan membuat sebuah blog yang melarang beberapa bagian seperti membuat, memperbaharui dan menghapus artikel kecuali oleh pengguna yang terotorisasi.
Memulai
Pertama dimulai dengan membuat sebuah aplikasi Rails baru.
1 |
rails new pundit-blog -T
|
Tanda -T
memberi tahu Rails untuk membuat aplikasi baru tanpa test suite bawaan. Menjalankan perintah tersebut akan membuat aplikasi Railsmu dan memasan gems bawaan.
Lalu, tambahkan gems berikut ke gemfile mu. Kamu akan menggunakan bootsrap-sass untuk layout aplikasimu, dan Devise akan mengurus otentikasi pengguna.
1 |
#Gemfile |
2 |
|
3 |
... |
4 |
gem 'bootstrap-sass' |
5 |
gem 'devise' |
Jalankan perintah untuk memasang gem
1 |
bundle install
|
Sekarang ganti nama app/assets/stylesheets/application.css
menjadi app/assets/stylesheets/application.scs
. Dan tambahkan baris berikut ke bootstrap import.
1 |
#app/assets/stylesheets/application.scss |
2 |
|
3 |
... |
4 |
@import 'bootstrap-sprockets'; |
5 |
@import 'bootstrap'; |
Buat sebuah partial bernama _navigation.html.erb
untuk menjaga kode navigasi; partial harus diletakkan di app/views/layouts. Buat partial tampak seperti di bawah.
1 |
#app/views/layouts/_navigation.html.erb |
2 |
|
3 |
<nav class="navbar navbar-inverse"> |
4 |
<div class="container"> |
5 |
<div class="navbar-header"> |
6 |
<%= link_to 'Pundit Blog', root_path, class: 'navbar-brand' %> |
7 |
</div>
|
8 |
<div id="navbar"> |
9 |
|
10 |
<ul class="nav navbar-nav pull-right"> |
11 |
<li><% link_to 'Home', root_path %></li> |
12 |
<ul class="nav navbar-nav pull-right"> |
13 |
<% if user_signed_in? %> |
14 |
<li><%= current_user.email %></li> |
15 |
<li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li> |
16 |
<% else %> |
17 |
<li><%= link_to 'Log In', new_user_session_path %></li> |
18 |
<li><%= link_to 'Sign Up', new_user_registration_path %></li> |
19 |
<% end %> |
20 |
</ul>
|
21 |
</ul>
|
22 |
</div>
|
23 |
</nav>
|
Untuk navigasi yang digunakan, kamu perlu merendernya di dalam layout aplikasi. Tweak layout aplikasimu menjadi seperti di bawah.
1 |
#app/views/layouts/application.html.erb |
2 |
|
3 |
<!DOCTYPE html>
|
4 |
<html>
|
5 |
<head>
|
6 |
<title>Pundit-Blog</title> |
7 |
<%= csrf_meta_tags %> |
8 |
|
9 |
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> |
10 |
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> |
11 |
</head>
|
12 |
|
13 |
<body>
|
14 |
<%= render "layouts/navigation" %> |
15 |
<div id="flash"> |
16 |
<% flash.each do |key, value| %> |
17 |
<div class="flash <%= key %>"><%= value %></div> |
18 |
<% end %> |
19 |
</div>
|
20 |
<div class="container-fluid"> |
21 |
<%= yield %> |
22 |
</div>
|
23 |
</body>
|
24 |
</html>
|
Membuat User Model
Jalankan perintah untuk memasang Devise.
1 |
rails generate devise:install |
Sekarang buat User Model mu.
1 |
rails generate devise User |
Migrasikan database-mu.
1 |
rake db:migrate |
Membuat Article Resources
Jalankan perintah untuk membuat article resource mu.
1 |
rails generate scaffold Articles title:string body:text |
Ini akan membuat ArticleController
dan Article Model. Juga akan membuat views yang diperlukan.
Sekarang migrasikan database-mu menggunakan:
1 |
rake db:migrate |
Buka app/views/articles/_form.html.erb
dan buat menjadi tampak seperti di bawah ini.
1 |
#app/views/articles/_form.html.erb |
2 |
|
3 |
<%= form_for(article) do |f| %> |
4 |
<% if article.errors.any? %> |
5 |
<div id="error_explanation"> |
6 |
<h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2> |
7 |
|
8 |
<ul>
|
9 |
<% article.errors.full_messages.each do |message| %> |
10 |
<li><%= message %></li> |
11 |
<% end %> |
12 |
</ul>
|
13 |
</div>
|
14 |
<% end %> |
15 |
|
16 |
<div class="field"> |
17 |
<%= f.label :title %> |
18 |
<%= f.text_field :title %> |
19 |
</div>
|
20 |
|
21 |
<div class="field"> |
22 |
<%= f.label :body %> |
23 |
<%= f.text_area :body %> |
24 |
</div>
|
25 |
|
26 |
<div class="actions"> |
27 |
<%= f.submit %> |
28 |
</div>
|
29 |
<% end %> |
Untuk index file, dia harus tampak seperti ini.
1 |
#app/views/articles/index.html.erb |
2 |
|
3 |
<table class="table table-bordered table-striped table-condensed table-hover"> |
4 |
<thead>
|
5 |
<tr>
|
6 |
<th>Title</th> |
7 |
<th>Body</th> |
8 |
<th colspan="3"></th> |
9 |
</tr>
|
10 |
</thead>
|
11 |
|
12 |
<tbody>
|
13 |
<% @articles.each do |article| %> |
14 |
<tr>
|
15 |
<td><%= article.title %></td> |
16 |
<td><%= article.body %></td> |
17 |
<td><%= link_to 'Show', article %></td> |
18 |
<td><%= link_to 'Edit', edit_article_path(article) %></td> |
19 |
<td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td> |
20 |
</tr>
|
21 |
<% end %> |
22 |
</tbody>
|
23 |
</table>
|
24 |
|
25 |
<br>
|
26 |
|
27 |
<%= link_to 'New article', new_article_path %> |
Kode di atas menyusun artikel-artikel di halaman indeks ke dalam format tabel untuk membuatnya lebih mudah ditampilkan.
Buka berkas routes mu dan tambahkan routes untuk resource articles.
1 |
#config/routes.rb |
2 |
|
3 |
... |
4 |
resources :articles |
5 |
root to: "articles#index" |
Mengintegrasikan Pundit
Tambahkan Pundit gem ke Gemfile mu.
1 |
#Gemfile |
2 |
|
3 |
... |
4 |
gem 'pundit' |
Jalankan perintah untuk memasang.
1 |
bundle install
|
Integrasikan Pundit dengan aplikasimu dengan menambahkan baris berikut ke ApplicationController mu.
1 |
#app/controllers/application_controller.rb |
2 |
|
3 |
... |
4 |
include Pundit |
5 |
... |
Jalankan generator Pundit
1 |
rails g pundit:install |
Ini akan membuat folder app/policies yang berisi class dasar dengan policies. Setiap policy adalah class Ruby dasar.
Seperti inilah tampilan dari base class policy.
1 |
#app/policies/application_policy.rb |
2 |
|
3 |
class ApplicationPolicy |
4 |
attr_reader :user, :record |
5 |
|
6 |
def initialize(user, record) |
7 |
@user = user |
8 |
@record = record |
9 |
end |
10 |
|
11 |
def index? |
12 |
false |
13 |
end |
14 |
|
15 |
def show? |
16 |
scope.where(:id => record.id).exists? |
17 |
end |
18 |
|
19 |
def create? |
20 |
false |
21 |
end |
22 |
|
23 |
def new? |
24 |
create? |
25 |
end |
26 |
|
27 |
def update? |
28 |
false |
29 |
end |
30 |
|
31 |
def edit? |
32 |
update? |
33 |
end |
34 |
|
35 |
def destroy? |
36 |
false |
37 |
end |
38 |
|
39 |
def scope |
40 |
Pundit.policy_scope!(user, record.class) |
41 |
end |
42 |
|
43 |
class Scope |
44 |
attr_reader :user, :scope |
45 |
|
46 |
def initialize(user, scope) |
47 |
@user = user |
48 |
@scope = scope |
49 |
end |
50 |
|
51 |
def resolve |
52 |
scope |
53 |
end |
54 |
end |
55 |
end |
Membuat Article Policy
Sekarang kamu harus menulis policy mu sendiri. Untuk tutorial ini, kamu ingin untuk hanya mengizinkan pengguna terdaftar untuk membuat artikel baru. Di samping itu, hanya pembuat dari sebuah artikel yang bisa mengubah dan menghapus artikel.
Untuk mencapainya, article policy mu akan tampak seperti ini.
1 |
#app/policies/article_policy.rb |
2 |
|
3 |
class ArticlePolicy < ApplicationPolicy |
4 |
def index? |
5 |
true
|
6 |
end
|
7 |
|
8 |
def create? |
9 |
user.present? |
10 |
end
|
11 |
|
12 |
def update? |
13 |
return true if user.present? && user == article.user |
14 |
end
|
15 |
|
16 |
def destroy? |
17 |
return true if user.present? && user == article.user |
18 |
end
|
19 |
|
20 |
private
|
21 |
|
22 |
def article |
23 |
record
|
24 |
end
|
25 |
end
|
Di atas kita, kita mengizinkan siapaun ( pengguna terdaftar dan tidak terdaftar ) untuk melihat laman indeks. Unbtuk membuat artikel baru, sebuah pengguna harus terdaftar. Kamu menggunakan user.present?
untuk mencari tahu apabila pengguna mencoba untuk melakukan aksi yang terdaftar.
Untuk memperbaharui dan menghapus, kamu harus pastikan hanya pembuat artikel yang bisa melakukan aksi tersebut.
Pada tahap ini, kamu perlu membuat sebuah hubungan antara Article dengan model User.
Kamu melakukannya dengan membuat sebuah migrasi baru.
1 |
rails generate migration add_user_id_to_articles user:references |
Lalu, migrasikan database-mu dengan menjalankan kode:
1 |
rake db:migrate |
Buka model User dan tambahkan baris yang membungkus hubungan.
1 |
#app/models/user.rb |
2 |
|
3 |
... |
4 |
has_many :articles |
Model Article-mu harus memiliki ini.
1 |
#app/models/article.rb |
2 |
|
3 |
... |
4 |
belongs_to :user |
Sekarang kamu harus memperbaharui ArticlesController sehingga ini tersinkron dengan apa yang telah kamu lakukan sejauh ini.
1 |
#app/controllers/articles_controller.rb |
2 |
|
3 |
class ArticlesController < ApplicationController |
4 |
before_action :set_article, only: [:show, :edit, :update, :destroy] |
5 |
|
6 |
# GET /articles |
7 |
# GET /articles.json |
8 |
def index |
9 |
@articles = Article.all |
10 |
authorize @articles |
11 |
end
|
12 |
|
13 |
# GET /articles/1 |
14 |
# GET /articles/1.json |
15 |
def show |
16 |
end
|
17 |
|
18 |
# GET /articles/new |
19 |
def new |
20 |
@article = Article.new |
21 |
authorize @article |
22 |
end
|
23 |
|
24 |
# GET /articles/1/edit |
25 |
def edit |
26 |
end
|
27 |
|
28 |
# POST /articles |
29 |
# POST /articles.json |
30 |
def create |
31 |
@article = Article.new(article_params) |
32 |
@article.user = current_user |
33 |
authorize @article |
34 |
|
35 |
respond_to do |format| |
36 |
if @article.save |
37 |
format.html { redirect_to @article, notice: 'Article was successfully created.' } |
38 |
format.json { render :show, status: :created, location: @article } |
39 |
else
|
40 |
format.html { render :new } |
41 |
format.json { render json: @article.errors, status: :unprocessable_entity } |
42 |
end
|
43 |
end
|
44 |
end
|
45 |
|
46 |
# PATCH/PUT /articles/1 |
47 |
# PATCH/PUT /articles/1.json |
48 |
def update |
49 |
respond_to do |format| |
50 |
if @article.update(article_params) |
51 |
format.html { redirect_to @article, notice: 'Article was successfully updated.' } |
52 |
format.json { render :show, status: :ok, location: @article } |
53 |
else
|
54 |
format.html { render :edit } |
55 |
format.json { render json: @article.errors, status: :unprocessable_entity } |
56 |
end
|
57 |
end
|
58 |
end
|
59 |
|
60 |
# DELETE /articles/1 |
61 |
# DELETE /articles/1.json |
62 |
def destroy |
63 |
@article.destroy |
64 |
respond_to do |format| |
65 |
format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' } |
66 |
format.json { head :no_content } |
67 |
end
|
68 |
end
|
69 |
|
70 |
private
|
71 |
# Use callbacks to share common setup or constraints between actions. |
72 |
def set_article |
73 |
@article = Article.find(params[:id]) |
74 |
authorize @article |
75 |
end
|
76 |
|
77 |
# Never trust parameters from the scary internet, only allow the white list through. |
78 |
def article_params |
79 |
params.require(:article).permit(:title, :body, :user_id) |
80 |
end
|
81 |
end
|
Pada titik ini di aplikasimu, kamu telah sukses mengimplementasikan policy yang akan menghalangi beberapa bagian dari aplikasimu untuk beberapa pengguna.
Kamu bisa menambahkan pesan error standar yang akan tampil kapanpun seorang pengguna tak terdaftar mencoba untuk memasuki halaman terbatas. Untuk melakukannya tambahkan hal berikut ke ApplicationController
mu.
1 |
#app/controllers/application_controller.rb |
2 |
|
3 |
... |
4 |
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized |
5 |
|
6 |
private |
7 |
|
8 |
def user_not_authorized |
9 |
flash[:warning] = "You are not authorized to perform this action." |
10 |
redirect_to(request.referrer || root_path) |
11 |
end |
Kode ini secara sederhana me-render teks biasa yang akan memberi tahu pengguna bahwa dia tidak terotorisasi untuk melakukan aksi tersebut.
Jalankan:
1 |
$ rails server
|
Untuk menjalankan server Rails, buka browsermu ke https://localhost:3000
untuk melihat yang kamu miliki.
Kesimpulan
Di tutorial ini, kamu telah belajar cara bekerja baik dengan Devise dan Pundit. Kamu bisa membuat policy yang hanya mengizinkan pengguna terotorisasi untuk melihat bagian tertentu dari aplikasi. Kamu juga telah membuat sebuah pesan error dasar yang tampil ketika pengguna tidak terotorisasi mencoba mengakses bagian terlarang dari aplikasi.
Kamu bisa belajar lebih banyak mengenai Pundit dengan mengecek laman GitHubnya