İki Veri Arasında ki Benzerliğin Hesaplanması – Ruby

Bir RoR projesinde, iki farklı veritabanında bulunan ad-soyad ve kimlik numarası verilerinin birbirlerine ne kadar benzediğinin hesaplanması ve farklı veri setlerinin birbiriyle aynı olma olasılığının tespiti gibi bir ihtiyacım olmuştu.

Örneğin aşağıda ki iki veri birbirine ne kadar benziyor, bu kişiler gerçekten aynı kişiler olabilir mi hesaplamam gerekiyordu:

Bu hesaplamayı yapabilmek için Elasticsearch ve Ruby’nin marifetleri yeterli oldu. Elasticsearch’ün Ruby için sağladığı “records.each_with_hit” metodu ile herhangi iki veri birbirlerine ne kadar benziyor (yakınsıyor) kontrol edebilir ve benzerlik oranını 0-2 aralığında matematiksel olarak alabilirsiniz.

Örnek betik şu şekilde:

Betikte bulunan “1.2” değeri, tamamen benim istediğim yakınlık derecesini ifade etmekte. Daha yüksek yakınlığa sahip verileri tespit etmek için bu değeri arttırabilir, daha geniş bir aralık almak için azaltabilirsiniz. Betik çalıştıktan sonra şöyle bir çıktı veriyor:

Kolaylıklar.

Ruby on Rails ve Güvenlik – CSRF ve API Talepleri

CSRF ve Authenticity Token

Rails’te herhangi bir formdan POST request yaparken authenticity_token parametresi de gönderilmekte. Bu parametre CSRF saldırılarına karşı Rails’in kullandığı default savunma mekanizmasının bileşenlerinden biri. Aşağıda POST işlemi yapan örnek bir form bulunuyor, hidden field olarak authenticity_token gönderildiğini görebilirsiniz.

İşin hikayesi ise şöyle; Rails POST request’leri için user’ın session bilgisini temel alarak rastgele bir hash oluşturup bunu form içerisinde hidden field olarak ekliyor ve request esnasında gönderiyor. Oluşturulan token form submit edildiğinde diğer parametrelerle birlikte gönderiliyor ve web uygulaması tarafından işleniyor.

POST request’in akabinde web sunucumuz gönderilen token ile mevcut kullanıcının session bilgisini kıyaslıyor ve eşleşmeyen bir durum bulunduğunda bunu saldırı olarak kabul ediyor. Eğer Rails’in default davranışını değiştirmediyseniz request’in hataya düşmesi gerekiyor. Aşağıda POST işlemi yapan bir form görebilirsiniz. Henüz token’ı kurcalamadık.

POST işlemi yapan form
POST işlemi yapan form

Session’a erişme şansı olmayan bir saldırgan gibi davranıp token’ı değiştirelim ve neler olduğunu inceleyelim (Token’ın son 2 karakteri olan “==” karakterlerini “??” olarak değiştirdim):

Parametreleri değiştirilmiş form.
Parametreleri değiştirilmiş form.

Sonuç olarak beklendiği üzere uygulama exception’a düştü ve hata verdi =>

Rails CSRF protection

Uygulamanın bu davranışı göstermesini sağlayan ise application_controller’da bulunan “protect_from_forgery” metodu.

Kod aslında yaptığı işi açıkça söylüyor. Diğer bir yandan da API’lar için null_session kullanmamızı öneriyor. Eğer uygulamanız içerisinde bir API barındıracaksanız protect_from_forgery with: :exception yaptığınız request’leri hataya düşürecek çünkü Rails authenticity_token’ı sizin request’iniz içerisine eklemeyecek. Deneyelim:

Konfigürasyon: 

API Request:

API Request Sonucu:

with: :exception ile uygulamayı hataya düşürme davranışına alternatif olarak reset_session ve null_session seçeneklerini de kullanabilirsiniz:

  • :reset_session – Oturumu sıfırlar.
  • :null_session – Request esnasında yeni boş bir oturum sağlar ancak mevcut oturumu sıfırlamaz.

Detaylardan bir kaç ilginç nokta:

  1. Yeni bir Rails uygulaması oluşturduğunuzda “protect_from_forgery” metodu with: :exception ayarlı olarak geliyor.
  2. Rails 4’te => “protect_from_forgery” metodunu tek başına çağırdığınızda default davranışı “reset_session” şeklinde. Bknz: source.
  3. Rails 3’te => “protect_from_forgery” metodunu tek başına çağırdığınızda default davranışı “null_session” şeklinde.

GET mi – POST mu?

protect_from_forgery metodu sadece POST metodu için çalışmakta. Diğer bir değişle GET request’i için CSRF koruması uygulanmıyor. Bu durum Rails kaynak kodu içerisinde şu şekilde ifade ediliyor:

“GET requests are not protected since they don’t have side effects like writing to the database and don’t leak sensitive information.”

Sonuç olarak GET request’lerine hassas bilgiler döndürmemek ve GET request’lerinden gelen parametreleri veritabanına yazmamak önem taşıyor. Eğer hatalı bir kurgu izlemiş ve veritabanı ile olan ilişkilerinizi GET üzerine kurmuşsanız öncelikle bu hatalı kurguyu değiştirmeniz gerekiyor.

API Uygulamaları ve CSRF

Web uygulamanız içerisinde API sunma niyetindeyseniz yukarıda bahsettiğim şekilde “with: :exception” API request’lerinizi hataya düşürecektir çünkü browser üzerinden yapılmayan request’lerde Rails authenticity_token parametresini request’e ekleyemeyecek. O halde hem API  request’lerini hemde normal uygulama request’lerini aynı anda nasıl güvende tutacağız?

1) JSON request’lerini protect_from_forgery’den muaf tut

Request =>

Response =>

Response içerisinde CSRF token’ın doğrulanamadığını söylüyor ancak :exception çalıştırmadığımız için ve default davranış “reset_session” olduğu için API requestimiz başarıyla çalışıyor ve yanıtını alıyor. Bu uyarı sadece loglanıyor ve client’ın sorgularına engel olmuyor. Diğer bir yandan form üzerinden POST yaparken token’ı değiştirirsek uygulama session’ı sıfırlayacak ve POST request’imiz iptal edilecek.

Request =>

Response =>

Bir önce ki POST request ile bu POST request arasında fark, ilk request’in /query yoluna yapılmış, ikinci request’in ise /query.json yoluna yapılmış olması. Dolayısıyla request bir JSON isteği olduğu için uygulama artık loglara token doğrulanamadı hatasıda basmıyor.

Dezavantaj => Eğer  :exception kullanmak istiyorsanız /query yolunun default davranışından fedakarlık etmeniz gerekecek çünkü uygulama bu yolda hata verecek.

Sonuç olarak vardığım en sağlıklı sonuç şu konfigürasyon ile oldu =>

2) API controller’larını protect_from_forgery’den muaf tut

Eğer API request’lerini idare eden controller sadece JSON sunuyorsa callback’lerden faydalanabiliriz. Eğer controller hem bir template render ediyor hemde JSON sunuyorsa CSRF korumasını kapatmak güvenlik zaafiyetine yol açacaktır.

Sadece index action’unu muaf tut =>

Controller’ı muaf tut =>

Diğer detaylar için metodun kaynak kodunu okuyabilirsiniz:

https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/request_forgery_protection.rb

 

Ruby on Rails ve Güvenlik – Routes

Rails uygulamalarında route’ların güvenliğini sağlarken Devise gibi authentication GEM’lerinin sağladığı helper’lardan ve lambda metodundan yararlanabilirsiniz. Örneğin sadece super_admin rolüne sahip kullanıcılara açık olmasını istediğiniz route’ları şu şekilde tanımlayabilirsiniz:

Artık admin/resque, admin/ip_lists ve admin/token_users yolları sadece super_admin’ler için erişilebilir olacak.

Model tarafında ise is_super_admin? metodunu şu şekilde tanımlı:

Selamlar.

Rails – N+1 Sorguları ve Çözümü

Veritabanınızda 5 tane “exam_center” kaydı olduğunu varsayalım.

ve aşağıda ki gibi bir model yapınız olduğu;

Bu exam_center’ların tamamını bir index sayfasında göstermek istediğinizde, her bir exam_center için 1 tane country, 1 tanede city sorgusu yapılacaktır. Yani her bir kayıt için view’dan çağırdığınız ilişki sayısı kadar sorgu yapılacak. 5 tane veritabanı kaydımız olduğu için, toplamda 10 sorgu çekilecek ve ek olarak ilk sorgu olan exam_center’ın kendisinin sorgulanmasıyla birlikte toplamda 11 sorgu edecektir. Buna kısaca N+1 durumu ismi verilir.

Uygulamanız içerisinde controller ve view dosyalarınızın bu şekilde olduğunu varsayalım:

Bu yapıda kurgulanmış bir sistemde index metodunu çağırdığımda loglarıma şu sorgular düşüyor:

5 veritabanı kaydı için çekilen 11 sorguyu görebilirsiniz.

includes Metodu

N+1 sorgu durumundan kurtulmak için “includes” metodu kullanılır.

Controller’ı bu şekilde düzenleyerek tekrar logları kontrol edelim:

Yapılan sorgu sayısı 7’ye düştü ancak hala Country için yapılan sorgularda bir değişiklik bulunmuyor. Burada “nested” bir lişki yapısı olduğu için dökümantasyonu takip ederek controller’ı tekrar düzenlememiz gerekecek:

Tekrar loglarımızı kontrol ettiğimizde yapılan sorgu sayısının 3’e düştüğünü ve sorgular için harcanan toplam sürenin azaldığını görebilirsiniz:

Hepsi bu kadar. Detaylar için Rails dökümantasyonuna göz atabilirsiniz:

http://guides.rubyonrails.org/active_record_querying.html

GEM – Pundit ile Authorization (Cancan Alternatifi)

Neden Cancan değilde Pundit?

  • Cancan 1 yılı aşkın süredir güncellenmiyor. Pundit sık sık güncelleniyor.
  • GEM’in geliştiricisi olan Ryan Bates’in uzun zamandır kayıplara karışmış durumda. Ryan Bates ayrıca Railscasts için 2 Eylül’de dönüş yapacağını söylemişti ancak 11 Temmuz itibariyle hala dönüş yapmadı ve kendisinden haber yok. Pundit’in arkasında İsveç kökenli elabs firması var.
  • Cancan’i başka sürdüren, sahip çıkan kimsenin olmaması sebebiyle açık olan onlarca issue bulunuyor. An itibariyle cancan’de 257, pundit’te ise 20 tane açık issue var. Cancan’e yapılan pull request’ler uzun bir süredir merge edilmemiş.
  • Rails 4.1 yayında olmasına rağmen cancan hala Rails 3 üzerini ve strong parametreleri desteklemiyor. Pundit ise Rails 4 destekliyor ve pure ruby yapısından dolayı uzunca bir süre versiyon sorunu çıkarmayacak gibi duruyor.
  • Cancan’in çok magic bir yapısı var. Pundit’te işler daha sizin kontrolünüzde.
  • Pundit cancan’e göre çok daha hafif ve hızlı çalışan bir GEM.
  • Pundit’in bağımlılıkları Cancan’in bağımlılıklarından daha az.
  • Pundit sayesinde authorization yapınızı hiç modele ve controller’a bulaşmadan – dolayısıyla fat controller’lar – modeller yaratmadan kurabiliyorsunuz.

Meraklısına ilave okumalar:

  1. Cancan vs. Pundit: http://www.distilnetworks.com/cancan-vs-pundit-choose-pundit-authorization/
  2. Alternative for Cancan: http://stackoverflow.com/questions/7213927/alternative-for-cancan/10235613#10235613
  3. Cookie HQ’ın Pundit Deneyimleri: http://cookieshq.co.uk/posts/pundit/
  4. Cancan’den Pundit’e Migration: http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/

Kurulum

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

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

Kısa Bir Ara (Planlama)

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

auth

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

    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

    gibi sorgu yaparsınız. Roller ve User modelleri HABTM 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ı ve onlarca rolün olacağı bir sistemde ikinci yöntemi tercih etmenizi öneririm.

Kullanım

Generator’ın oluşturduğu dosya/lar 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.

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;

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

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:

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;

Scopelar ve diğer konular için GEM dökümantasyonunu takip edebilirsiniz:

https://github.com/elabs/pundit

Kolay gelsin.

Rails'te CSV İşlemleri – Roo ve SmarterCSV GEM'leri

Roo GEM

CSV, XLS ve ODS’yi başarıyla okuyor, ancak tüm yaptığı bundan ibaret – başka bir özelliği yok.

Roo GEM (CSV Load)

Roo GEM (CSV Read Örneği)

SmarterCSV GEM

Smarter_CSV sadece CSV ile çalışıyor ancak özellik bakımından çok daha üstün.

SmarterCSV (CSV Read)

process redis’ten tanıdık. Bu betik tamda istenilen şekilde hash döndürür:

Burada göründüğü üzere smarter_csv bir takım normalizasyon işlemleride yapıyor. Örneğin “TC Kimlik No” başlığını downcase ve snakecase yaparak tc_kimlik_no şekline sokmuş ancak “Öğrenci No” için aynısını başarılı yapamamış “Öğrenci_no” gibi birşey ortaya çıkmış. Bu durum key_mapping yaparken önemli.

SmarterCSV (Key Mapping)

:Öğrenci_no yüzünden çalışmayacağını düşündüğüm betik sorunsuz çalışıyor ve şunu döndürüyor:

SmarterCSV (Chunk Size)

Bu parametre her bir dizide bulunacak value:key ikilisinin sayısını belirtiyor ve sonuçları buna göre döndürüyor.

SmarterCSV (Chunk Processing ve Virtual Attributes)

CSV dosyamızı bizim belirlediğimiz miktarda yığınlara (chunk) bölerek işletebiliriz.

CSV Dosyamız=>

Betik =>

Yanıt =>

SmarterCSV (Single Chunk Processing)

Aynı CSV’yi parçalara bölmeden direk tek parça olarak işletmek için;

Yanıt olarak bu döner =>

SmarterCSV (Modellere Yazmak – Toplu)

Import dosyası =>

Deneme amacıyla ilgili işlemleri Participation modelinin index controller’ına ekledim.

Bu action’dan beklenen, Participations sayfası her ziyaret edildiğinde beklenen şey CSV’nin işlenmesiydi. Sayfayı ziyaret ettiğimde kayıtlar eklenmedi, çünkü modelde bir takım validasyonlar buna engel oluyordu.

Bu kısıtlamamdan dolayı CSV işlenmesine rağmen kayıtlar işlenmedi — ancak ve ancak request arka planda asılı kalmış bekliyordu. Bir süre sonra sistemde zaten olan kayıtları temizledim ve CSV’den gelen kayıtlar tıkır tıkır veritabanına yazıldı.

Yani özetle herhangi bir sebeple process tamamlanamazsa iptal olmuyor ve askıda kalıyor, şartlar uygunlaşıncada işlem kaldığı yerden devam ediyordu. Bu hem iyi, hemde tehlikeli bir durum!

SmarterCSV (Modellere Yazmak – Parça Parça)

Aynı işlemi chunk’lar halinde parça parça veritabanına yazmak için şöyle yapılabilir =>

SmarterCSV (Modellere Yazmak – Resque ile)

Henüz bunu denemedim fakat dökümantasyona göre bu şekilde olması gerekiyor =>

BOM’lu CSV’lerde Unicode

Windows’ta aksi belirtilmediği sürece tüm dosyala BOM’lu yazılıyor. BOM’lu CSV dosyalarında unicode problem çıkarttığı için şöyle bir şey kullanılmalıymış:

GEM – Unicode Utils

Ruby Upcase Metodunda Türkçe Karakter Problemi

MERNİS’e yaptığım bir request’te, Ruby’nin upcase metodu kaynaklı Türkçe karakter problemi ile karşılaştım.

SOAP request’ini incelediğimde “ü” karakterinin büyük harfe çevrilemediğini gördüm:

Çözüm (Unicode Utils)

unicode_utils GEM’ini kurun.

* Alternatif olarak “unicode” GEM’ini de kurabilirsiniz. Aynı ölçüde başarılılar.

Deneme betiği hazırlayalım:

Betiği çalıştırdığınızda sonucun başarılı olduğunu göreceksiniz:

Rails – Action Pack – Kavramlar

Active Record modellerden sorumlu olan Rails kütüphanesi olduğu gibi, Action Pack ise view ve controller’dan sorumlu Rails kütüphanesidir.

Action Controller

  • Rails uygulamamızda gerçekleşen her türlü etkileşim ve hareket controller tarafından kontrol edilir.
  • Rails’te controller’lar Ruby sınıfları olarak tasarlanırlar.
  • Controller içerisinde, çeşitli action’lar için yazılmış metodlar bulunur.

Aksi belirtilmediği sürece herhangi bir Ruby sınıfı içerisindeki metodların durumu “public“‘tir yani herhangi bir kişi tarafından erişilebilir durumdadırlar. Eğer bir metodu “private” olarak tanımlarsanız, o metod yalnızca ait olduğu class içerisinde kullanılabilir olur. Private olarak tanımlanmış bir metodu ait olduğu class dışarısından çağırmaya çalışırsanız “NoMethodError” hatası döner.

Rails’te sıklıkla ve aksi belirtilmedikçe, bir action çalıştıktan sonra bir view render edilir. Action Controller, convention’ın bir sonucu olarak render edeceği view’ı action’ın adıyla bulmaya çalışır. Örneğin “app/controllers/articles_controller.rb” controller’ı içerisinde bulunan “new” metodu, convention gereği “/app/views/articles/new.html.erb” sayfasını render eder.

Action View

Controller ve View’ın iletişim halinde kalabilmesi için paylaşılan değişkenlerden (shared variables) faydalanırız. Bu değişlenlerin tanımı “@” sembolü ile başlar ve controller’da tanımlandıktan sonra view’dan çağırılabilirler.

Burada @articles değişkenimiz controller’dan geliyor.

Embeded Ruby (ERB)

ERB sayesinde Ruby kodlarımızı view içerisinde kullanabiliriz. View dosyalarımız içerisinde <% %> (evaluation embedding tags) ve <%= %> (output embedding tags) etiketlerini kullanarak Ruby kodlarının view’da işlemesini sağlarız. Bu iki etiketten yalnızca output türünde “=” işareti bulunduğuna dikkat ediniz. Output etiketi yani <%= %> ile, işlenen Ruby koduna ait çıktının print edilmesini istediğimizi belirtiriz. Oysa ki evaluation etiketleri yani <% %> kullanıldığında, etiketler içerisindeki kod işlenir ve süreç tamamlanır, yani herhangi bir print işlemi yapılmaz.

Helpers

Her ne kadar ERb ile view’da çeşitli Ruby kodlarını çalıştırabiliyor ve controller’da view’a dair işler yaptırabiliyor olsakta, bu durum MVC’nin yapısı ve mantığına terstir. Controller’ın asıl işi view kodu üretmek olmadığı gibi, view’in işi de mantıksal süreçleri çözümlemek değildir. View ile controller arasındaki bu boşluğu dolduran yapılara “helper” adı verilir. Helper’lar sorun yaratan bu boşluğu doldurarak controller ve view arasında bir köprü görevi görürler. Her bir controller’ın kendine ait helper’ı vardır. Örneğin “articles_controller.rb” için “articles_helper.rb” dosyası.

Action Pack Request Döngüsü

Action Pack request-response döngüsü aşağıdaki adımlardan oluşur:

  1. Rails uygulamamız dış dünyadan (çoğunlukla browser) bir request alır.
  2. Routing, çalışması gereken controller ve action’ı tespit etmek için gelen request’i parçalar.
  3. Request’i ele alacak controller belirlenir ve ilgili metodlar çağırılır.
  4. Controller model ile iletişime geçer. Çoğunlukla bir CRUD süreci gerçekleştirir.
  5. Browser’a (veya request’i yapan her ne ise) render edilecek sayfayı içeren veya yalnızca redirect’ten ibaret bir response döndürülür.

rails-action-pack-request-response-cycle

Rails – Callbacks

Kullanıcılardan herhangi biri hesabını iptal ettiği zaman adminlere bilgilendirme maili gitmesi, bir işlem tetiklendiğinde – başka bir işlemin de gerçekleşmesi veya bir model nesnesi oluşturduğunuzda, başka bir model nesnesinin de onla ilişkili olarak oluşması gibi durumları tanımlarken ihtiyaç duyduğumuz metodların en sık kullanılanları Rails’te 6 tanedir. Bunlar:

  • before_create
  • after_create
  • before_save
  • after_save
  • before_destroy
  • after_destroy

Zaten bu ifadelerin ne iş yaptığı isimlerinden de anlaşıldığı için tekrarlamayacağım.

before_” ile başlayan herhangi bir callback false döndürdüğü taktirde, uygulamanızın çalışma sürecini durdurabileceği için dikkatli kullanılmalıdır.

İlerde uygulamanız dallanıp budaklandığında modeliniz kayıt işlemi yapmıyorsa öncelikli olarak “before_*” ifadelerini kontrok faydalı olabilir.

Örneğin kullanıcılardan biri blog yazınıza yorum yazdığı zaman, yazının yazarına bilgilendirme emaili gitmesini istiyorsunuz varsayalım. Comment modeli içerisinde böyle bir durumu aşağıdaki gibi kurgularız:

create” işleminden sonra yapılmasını istediğiniz işleri doğrudan “after_create” metodu içerisine yazabilirsiniz. Ancak bu yöntem oldukça pratik olmasına rağmen, farklı işlemleri yapmak için çalışan kodları ard arda yazdığınızda kodunuzun okunabilirliği azalacaktır. Bu yüzden aşağıdaki gibi bir yol izleyerek her bir eylemi ayrı metod olarak tanımlamak ve daha sonra bunları after_create‘ten sonra virgülle ayırarak çağırarak daha mantıklı olur:

User Modelinin Güncellenmesi

User modelimizin altında bulunan “password” alanı, şifreleri plain-text olarak muhafaza ettiği için güvenlik zaaflarına sebebiyet verebilir. Bu tür hassas dataları her zaman encrypt etmek gereklidir. Bu yüzden öncelikle veritabanında ki “password” alanını “hashed_password” olarak tekrar adlandıralım:

Şimdi migration dosyamızı hazırlayalım:

Migration’ı çalıştıralım:

Şimdi ise User modelimizi bu değişikliğe uygun şekilde hazırlayalım:

Ruby’nin built-in kütüphanelerinden biri olan Digest ile şifreleri hash’leyebiliriz ancak bu örnekte kullanılan SHA1 şifreleme algoritmasının production ortamı için pekte kullanışlı olduğu söylenemez. Production için BCrypt kullanımı düşünülebilir:

https://github.com/codahale/bcrypt-ruby

Şimdi hazırladığımız User modelini satır satır inceleyelim:

  • require ‘digest’ => Şifreleri encrypt edebilmek için Ruby’nin built-in kütüphanelerinden biri olan Digest’i çağırdık.
  • attr_accessor :password => Burada Ruby’ye “password” için reader ve writer metodları oluşturmasını söyledik çünkü veritabanımızda artık “password” diye bir alan bulunmuyor ve bu yüzden de “password” isminde bir metod Active Record tarafından otomatik olarak oluşturulmuyor. Sonuç olarak “password“ü hala bir şekilde encrypt edilmeden önce set etmeye ihtiyacımız olduğu için kendi “niteleyicimizi (attribute)” oluşturduk. Bu niteleyici herhangi bir model niteleyicisi gibi çalışmasına rağmen, model kaydedildiğinde veritabanına kayıt edilmez.
  • before_save :encrypt_new_password => Burada ki “before_save” callback’i Active Record’a, kayıt yapmadan önce “encrypt_new_password” metodunu çalıştırmasını söylüyor. Burada kayıt yapmaktan kasıt hem create hemde update işlemi.
  • encrypt_new_password => Bu metod sayesinde yalnızca “password” alanı dolu ise şifrenin hashlenmesi sağlanır. Aksi halde mevcut olan şifre korunur. Böylece, kullanıcı şifresini güncellemek istemediğinde şifresini encrypt etmemiş oluruz. Eğer password alanı boş ise, “return if password.blank?” ile metoddan işlem yapmadan dönebiliriz. Ancak password alanı dolu ise metodumuz “self.hashed_password = encrypt(password)” ile girilen password’ü şifreyecektir.
  • encrypt => Bu metod Digest kütüphanesini kullanarak, ona gönderilen veriyi SHA1 ile şifreler. Ayrıca şifreleme sonucunu yani hash‘i döndürür.
  • password_required? => Kullandığımız validasyonları pratikleştirmek için hazırladığımız bir metodtur. Bu metod sayesinde “hashed_password” niteleyicisinin boş olup olmadığını – veya “password” erişicisinin (accessor) yeni bir şifrenin oluşturulmasında kullanılıp kullanılmadığını kontrol ettirebiliriz.
  • self.authenticate => Metodun ismine bakarak bunun bir class metodu olduğunu yani instance yerine doğrudan bir class üzeride çalıştığını söyleyebiliriz. Yani bu metoda bir instance üzerinden değil, doğrudan class üzerinden erişiriz. Örneklemek gerekirse; “@user = User.new & @user.authenticate” şeklinde erişmek yerine doğrudan “self.authenticate” şeklinde erişim sağlarız. Authenticate metodumuz biri e-mail adresi diğeri ise plain-text şifre olmak üzere iki tane argüman alıyor ve “find_by_email” metodu sayesinde ilgili e-mail adresi ile eşleşen kullanıcının bulunabilmesini sağlıyor. Eğer metod sayesinde kullanıcı bulunduysa zaten “user” değişkenine atanıyor, bulunamadıysa bu değişken “nil” olarak kalıyor. Metod içerisinde belirtilen “return user if user && user.authenticated?(password)” ifadesi sebebiyle metodumuz ancak user bulunabildiğinde ve bu user authenticated olduğunda true döndürecektir.
  • authenticated? => Bu metod basitçe; girilen password’ü önce hash‘ler, daha sonra ise saklanmış olan mevcut hash ile karşılaştırır. Eğer bunlar eşleşiyorsa true döndürür, eşleşmiyor ise false döndürür.

Hazırladığımız uygulamayı test edelim:

Sürecin Özeti

  • Authenticate metoduna email adresi ve plain-text şifre olmak üzere iki parametre gönderiyoruz.
  • Gönderdiğimiz şifre metod tarafından hashlenir ve veritabanında hash’li olarak tutulan şifreyle karşılaştırır.
  • Eğer hash’ler eşleşirse authentication başarılı olur, eşleşmez ise başarısız olarak “nil” döndürür.

Son olarak, eklediğimiz bu yeni özellikler için db/seeds.rb dosyamızı düzenleyelim:

KPS & MERNİS Sorgulama – Ruby, Savon ve SOAP

Geçtiğimiz hafta hem TTMesaj servisini hemde KPS’i (Kimlik Paylaşım Sistemi) Rails uygulamalarına entegre etme ihtiyacımızla birlikte, ismini pek sık duyduğum SOAP ile haşır neşir olmak durumunda kaldım. Üç-dört gün sonunda SOAP bana herhangi bir üçüncü dünya ülkesinin beyaz yakalı “plaza” çalışanını çağrıştırıyor. Bu çağrışımda ülkemizdeki SOAP servisleri ile haberleşen kod örneklerinin büyük çoğunlukla C#, ASP.Net ve Java ile yazılmış olmasının payı büyük.

Ruby tarafında ise Savon GEM’i olmasa halimiz yamanmış. Eğer sizde benim gibi SOAP hakkında zerre bilgi sahibi değilseniz ve bir şekilde bu servis ile haberleştirmek zorunda olduğunuz bir Ruby uygulamanız varsa ilk önce SOAP UI ile denemeler yapmanızı tavsiye ederim.

Bu uygulama içerisinde File -> New Project yolunu takip ederek, haberleşmeye çalıştığınız WSDL adresini yazın. Daha sonra WSDL’in sunduğu operasyonlara request aracı ile envai çeşit request yapmayı deneyerek dönen response’ları inceleyebilirsiniz.

İkinci bakmanız gereken yer ise Savon SOAP Client. Sırayla basitten zora, önce TTMesaj servisiyle haberleşmeyi – sonra ise KPS ile haberleşmeyi örnekleyeceğim.

TTMesaj SOAP Client

İlk önce WSDL üzerindeki mümkün tüm operasyonları listeyelim:

Request
Response

İlgili WSDL sayfasını ziyaret ederek bu operasyonlardan herhangi birini inceleyerek nasıl bir request yapmamız gerektiğini inceleyelim. Örneğin “send_single_sms” operasyonuna bakalım:

Parametre isimlerini dikkate alarak SOAP mesajımızı hazırlayalım:

Bu çağrı sonucunda mesajınızın başarıyla iletildiğine dair “OK*” işaretli bir response alacaksınız. Uygulamanın tümüne şuradan erişebilirsiniz:

https://github.com/msdundar/TTMesaj

KPS (Kimlik Paylaşım Sistemi)

KPS tarafında işler beni umduğumdan daha çok zorladı. 3-4 gün boyunca başarılı bir response almak için uğraştım. KPS servisi TTMesaj’dan farklı olarak, requestiniz içerisinde bir takım attribute’ler ve tamamen doğru kurgulanmış bir SOAP Header görmeyi bekliyor.

Öncelikle biraz önce yaptığımız gibi bir keşif turuna çıkalım. İlgili WSDL adresini ziyaret ettiğimizde beklenen request yapısını görebiliyoruz:

Burada farklı bir çok uygulama var. Öncelikle authentication “header” altında direk değil, header altında bulunan “KPSUserInfo” içerisinde “UserName” ve “Password” ile gerçekleşiyor.

Diğer farklı durum ise, “KPSUserInfo” ile birlikte “xmlns” adresininde attibute olarak verilmesi bekleniyor. Aynı durum body’de yine karşımıza çıkıyor. Orada ise “TCKimlikNoSorgu” altında mesajın verilmesi ve xmlns attribute’ü bekleniyor. Uzun araştırmalar sonucunda Savon ile bu işin nasıl yapılabileceğini buldum. Sonuçta ortaya şöyle bir uygulama çıktı:

Burada beni en çok zora sokan “:attributes!” hakkında Savon’un hiç dökümantasyon yapmamış olması, “username” yerine “UserName” gibi camel case takıntılı SOAP servisi, ve header ile body altında kullanılan alt parametreler oldu.

Uygulamanın tümüne şuradan erişebilirsiniz:

https://github.com/msdundar/kps_soap_client

Başarılar.