HELLO, I’M SERHAT AND THIS IS MY FANCY TITLE.

GEM - Pundit ile authorization (cancan alternatifi)

Ryan Bates'in sırra kadem basmadan önce geliştirdiği GEM'lerden biri de cancan. Ryan ortadan kaybolup cancan'i yetim bırakınca Bryan Rite önderliğinde bir grup geliştirici başka bir proje olan cancancan'i sahiplendi ve bunu geliştirmeye devam etti.

Yani cancancan sanılanın aksine cancan'den sonra ortaya çıkmış veya cancan'in yokluğuna karşılık geliştirilmiş bir GEM değil, 2009'dan beri hayatta olan bir proje. Ancak en aktif geliştirme sürecini ise 2014'ün Ocak ve Eylül ayları arasında (Ryan'ın ortadan kayboluşu) yaşıyor. Ryan nerede birileri tartışadursun, biz ayakları yere daha sağlam bakan alternatiflere gidelim.

Neden cancan veya cancancan değilde pundit?

  1. cancan 2003'ten beri güncellenmeyen ölü bir proje. cancancan'e ise sahip çıkan yok, sadece 3 kişi aktif commit yapıyor. pundit sık sık güncelleniyor ve arkasında İsveç kökenli elabs var.
  2. cancan'i veya cancancan'i başka sürdüren, sahip çıkan kimsenin olmaması sebebiyle açık olan onlarca issue bulunuyor. An itibariyle cancan'de 204 issue, cancancan'de 34 issue, pundit'te ise 19 issue açık durumda.
  3. cancan'e ve cancancan'e yapılan pull request'ler uzun bir süredir merge edilmemiş. pundit daha aktif güncellenmiş.
  4. Rails 5 yayında olmasına rağmen cancan hala Rails 3 üzerini ve strong parametreleri desteklemiyor. cancancan ve pundit ise Rails 4 ve 5'i destekliyor.
  5. cancancan daha Rails plug-in tadında, pundit ise pure ruby.
  6. cancancan'in çok magic bir yapısı var. pundit'te işler daha sizin kontrolünüzde ve uygulamadan daha yalıtık.
  7. pundit, cancancan'e göre çok daha hafif ve hızlı çalışan bir GEM.
  8. pundit'in bağımlılıkları cancancan'in bağımlılıklarından daha az.
  9. pundit sayesinde authorization yapınızı hiç modele ve controller'a bulaşmadan - dolayısıyla fat controller'lar veya modeller yaratmadan kurabiliyorsunuz.
  10. cancancan'in code climate skoru 2.4, pundit'in 3.8.
  11. Alternatifleri arasında Github'da en fazla yıldız alan proje pundit.

Meraklısına ilave okumalar:

Pundit kurulumu

gem "pundit"

application_controller'ı aşağıdaki şekilde düzenleyin:

class ApplicationController < ActionController::Base
  include Pundit
  protect_from_forgery
end

generator'ı çalıştırarak örnek dosyaları oluşturun:

$ rails g pundit:install

Kısa bir planlama arası

User isminde bir kullanıcı modeliniz olduğunu ve şöyle bir yetkilendirme sistemi hazırlamak istediğinizi varsayalım:

pundit-ile-authorization

Böyle bir yapıyı 2 şekilde kurgulayabilirsiniz.

  1. User modelinize her bir yetki ismi için boolean bir alan oluşturursunuz ve bu sayede user.admin? gibi sorgu yaparsınız.
  2. Roller için ayrı bir model oluşturursunuz ve her bir yetkiyi orada rol olarak tanımlarsınız ve user.has_roles?("Admin") gibi sorgu yaparsınız. Bu durumda roller ve user modeli HABTM (veya has_many :through) ilişkisine sahip olur.

Uygulamanızın ihtiyaçlarına göre herhangi bir yolu seçebilirsiniz. İlk yöntem daha basit authorization işlemleri için yeterliyken, kapsamlı, esnek ve daha sağlıklı bir yol izlemek isterseniz ikinci yöntemi tercih etmenizi öneririm.

Pundit kullanımı

Generator'ın oluşturduğu dosyalar GEM'in özelliklerini demo etmek ve size hazır bir iskelet yapıyı sunmak için düzenlendiğinden bunları incelemek kafanızı karıştırabilir, şimdilik boşverin.

Generator tarafından app/policies dizinine oluşturulan application_policy.rb dosyası authorization sisteminiz için default ayarlarınızı tanımlayacağınız dosyadır. Burada her bir CRUD işlemi için uygulamanın nasıl davranacağını kurgulayabilirsiniz.

# encoding: utf-8
class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  def index?
    (user.admin? || user.editor? || user.author? || user.contributor?) ? true : false
  end

  def new?
    (user.admin? || user.editor? || user.author?) ? true : false
  end

  def create?
    (user.admin? || user.editor? || user.author?) ? true : false
  end

  def edit?
    (user.admin? || user.editor?) ? true : false
  end

  def update?
    (user.admin? || user.editor?) ? true : false
  end

  def destroy?
    (user.admin?) ? true : false
  end
end

Koddan anlayabileceğiniz üzere her bir controller metodu için burada bir metod tanımlıyoruz ve hangi durumlarda true, hangi durumlarda ise false döndüreceğini belirtiyoruz.

Bu genellenmiş modelin işinizi görmediği durumlar için, bu modeli miras alan ayrı ayrı policy'ler oluşturabilirsiniz.

Örneğin City isminde bir modeliniz olduğunu ve burada application_policy'nin özelliklerini yine devralmak istediğinizi ancak ek ihtiyaçlarınız olduğunu varsayalım. Bu durumda app/policies altında city_policy.rb isminde bir dosya oluşturmanız yeterlidir. Veya;

rails g pundit:policy city

komutu ile bu modele özel bir policy dosyası hızlıca oluşturabilirsiniz.

class CityPolicy < ApplicationPolicy
  def edit?
    false
  end
end

edit? metodu city_policy içerisinde tekrar tanımlandığı için application_policy'de ki kullanımı göz ardı edilecektir. Buna benzer modele özel tanımlamaları kendi policy dosyası içerisinde yapabilirsiniz.

Şimdi tanımlamalarımız tamam olduğuna göre cities_controller.rb dosyasına gidelim:

class Admin::CitiesController < ApplicationController
  before_filter :authenticate_user!
  before_action :set_city, only: [:edit, :update, :destroy]

  def index
    @cities = City.paginate(:page => params[:page], :per_page => 15).order('name ASC')
    authorize @cities
  end

  def new
    @city = City.new
    authorize @city
  end
....
end

Ve hepsi bu kadar. Artık basit düzeyde fakat sorunsuz çalışan bir yetkilendirme sisteminiz var.

Permission denied hatasını ayıklama

Tanımladığınız durumlar dışında CRUD üzerinde yetkisiz bir işlem yapıldığında uygulamanız Pundit::NotAuthorizedError hatası vererek işlemi sonlandıracaktır. Bu hatayı yakalayarak, 403 sayfamıza yönlendirmesini sağlayalım:

application_controller.rb içerisinde aşağıdaki tanımlamayı yapınız:

class ApplicationController < ActionController::Base
  # Includes Authorization mechanism
  include Pundit

  protect_from_forgery with: :exception

  # Globally rescue Authorization Errors in controller.
  # Returning 403 Forbidden if permission is denied
  rescue_from Pundit::NotAuthorizedError, with: :permission_denied

  def permission_denied
    render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false)
  end
end

Scope'lar ve diğer konular için GEM dökümantasyonunu takip edebilirsiniz.

Kolay gelsin.


Share this post!


Blog Comments powered by Disqus.