yashke.com

nazgul @ October 13th, 2006

Obsługa użytkowników w RoR (cz 1)

1. Zaczynamy
Na początek wygenerujemy sobie wszystkie potrzebne elementy, czyli:

  • model:
    script/generate model User
  • kontroler oraz potrzebne akcje
    script/generate controller User singup login logout change_passwd forgot_passwd
  • mailer (przez niego będziemy wysyłać użytkownikowi nowe hasło)
    script/generate mailer Notifications forgot_password
  • I na koniec tabela w bazie:
    CREATE TABLE 'users'(
    'id' INT UNSIGNED NOT NULL,
    'login' VARCHAR (255) NOT NULL,
    'password_sha' VARCHAR (255) NOT NULL,
    'salt' VARCHAR (255) NOT NULL,
    'created_at' DATETIME NOT NULL,
    'email' VARCHAR (255) NOT NULL,
    PRIMARY KEY('id')
    )

    Tutaj można wykazać się inwencją i dodać więcej pół przechowujących np. imię, nazwisko, i inne dane o użytkowniku.


2. Model
Przejdźmy do pliku models/user.rb

Najpierw przed deklaracją klasy user dodamy linijkę załączającą funkcję hashującą sha1.
require 'digest/sha1'

Następne linijki będą odpowiadać za walidację pól w bazie (dodajemy to po deklaracji klasy):
validates_length_of :login, :within => 5..40
validates_length_of :password, :within => 10..40
validates_presence_of :login, :email, :password, :password_confirmation, :salt
validates_uniqueness_of :login, :email
validates_confirmation_of :password
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => “Błędny email”

Wartości te można zmieniać w zależności od potrzeb. W tym przypadku funkcje te sprawdzają czy pole login zawiera łańcuch o odpowiedniej długości (5-40 znaków), analogicznie jest sprawdzane pole password, czy pola login, email, password, password_confirmation i salt są wypełnione także, czy login i email nie istnieją już w innym rekordzie bazy oraz, czy password i password_confirmation mają tą samą wartość. Wyrażenie regularne w ostatniej linijce służy do potwierdzenia poprawnej składni wpisanego adresu email.
Jeśli dodałeś do bazy swoje pola, możesz zgodnie z podanymi regułami dodać ich sprawdzanie.

Teraz zabezpieczymy niektóre parametry. attr_protected nie pozwala na modyfikowanie wartości pola z parametrów przekazanych konstruktorowi, a attr_accessor zabrania dostępu od oznaczonych pól z poza klasy.
attr_protected :id, :salt
attr_accessor :password, :password_confirmation

Dodamy funkcje generującą losowe hasło (do mechanizmu salt i generowania nowego hasła) i pomocniczą funkcję hashującą
def self.random_string(len)
#tworzy losowe hasło z liter i cyfr
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
newpass = ""
1.upto(len) { |i| newpass

Funkcja przeładowująca przypisanie wartości do pola password (od razu będziemy tworzyć hash hasła):
def password=(pass)
@password=pass
self.salt = User.random_string(10) if !self.salt?
self.password_sha = User.encrypt(@password, self.salt)
end

I na koniec dwie funkcje, już typowo do obsługi użytkowników:
def self.authenticate(login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if User.encrypt(pass, u.salt)==u.hashed_password
nil
end

def send_new_password
new_pass = User.random_string(10)
self.password = self.password_confirmation = new_pass
self.save
Notifications.deliver_forgot_password(
self.email, self.login, new_pass)
end

Pierwsza funkcja sprawdza, czy podany login i hasło pasują do tych z bazy, a druga na żądanie generuje, zapisuje w bazie i wysyła mailem nowe hasło.

To wszystko co trzeba było zrobić w modelu, zajmijmy się teraz kontrolerem.

3. Kontroler
Mamy już wygenerowany schemat klasy UserController, więc zabierzemy się za jego wypełnianie.
Zaczniemy od akcji signup:
def signup
@user = User.new(@params[:user])
if request.post?
if @user.save
session[:user] = User.authenticate(@user.login, @user.password)
flash[:notice] = “Rejestracja przebiegła pomyślnie”
redirect_to :controller=> “news”
else
flash[:warning] = “Błąd podczas rejestracji”
end
end
end

Można zauważyć przypisanie komunikatu do tablicy flash[]. W następnej części napisze jak można wyświetlić te komunikaty na stronie.

Funkcja ta tworzy nowy obiekt User, wypełnia go danymi z formularza i zapisuje w bazie, przy okazji logując użytkownika.

Kod akcji login wygląda bardzo podobnie, tylko tutaj nie tworzymy nowego obiektu User:
def login
if request.post?
if session[:user] = User.authenticate(params[:user][:login], params[:user][:password])
flash[:notice] = “Zostałeś zalogowany”
redirect_to_stored
else
flash[:error] = “Błędny login/hasło”
end
end
end

Musimy także zadbać o wylogowanie użytkownika:
def logout
session[:user] = nil
flash[:notice] = ‘Zostałeś wylogowany’
redirect_to :action => ‘login’
end

I ostatnie 2 funkcje służące do wysłania nowego oraz zmienienia hasła:
def forgot_passwd
if request.post?
u= User.find_by_email(params[:user][:email])
if u and u.send_new_password
flash[:notice] = “Nowe hasło zostało wysłane emailem”
redirect_to :action=>’login’
else
flash[:error] = “Nie udało się wysłać hasła”
end
end
end

def change_passwd
@user=session[:user]
if request.post?
@user.update_attributes(:password=>params[:user][:password], :password_confirmation => params[:user][:password_confirmation])
if @user.save
flash[:notice]=”Hasło zmienione”
end
end
end

Chyba nic nie wymaga tu komentarza ;).

Teraz trzeba dodać jeszcze kilka funkcji pomocniczych do pliku application.rb.
def login_required
if session[:user]
return true
end
flash[:warning]=’Zaloguj się, aby kontynuować’
session[:return_to]=request.request_uri
redirect_to :controller => “user”, :action => “login”
return false
end


def redirect_to_stored
if return_to = session[:return_to]
session[:return_to]=nil
redirect_to_url(return_to)
else
redirect_to :controller=>’twój_kontroller’, :action=>’jakaś_akcja’
end
end

Druga funkcja pozwala na odesłanie do strony, która była wyświetlana przed wyświetleniem formularza logowania, może być przydatna także w innych zastosowaniach. Ważne! Pamiętaj aby podać domyślny kontroler i akcję.

4. Widoki
Na początek zajmiemy się ‘widokiem’ mailera, który ukrył się w views/notifications/forgot_password.rhtml – jest to treść maila jaki zostanie wysłany do użytkownika.
Dodamy tu prosty tekst, ty możesz wpisać coś ciekawszego:
Witaj <%= @login %>!
System wygenerował dla Ciebie nowe hasło: <%= @pass %>.

Można pokusić się o wysłanie w mailu tylko linka do strony na której będzie podane hasło, ale to już zostawiam wam.

Teraz zamieszczą przykładowy kod reszty widoków:
signup.rhtml
<%= start_form_tag :action=> "signup" %>
<%= error_messages_for 'user' %><br/>
<label for="user_login">Username</label><br/>
<%= text_field "user", "login", :size => 20 %><br/>
<label for="user_password">Password</label><br/>
<%= password_field "user", "password", :size => 20 %><br/>
<label for="user_password_confirmation">Password Confirmation</label><br/>
<%= password_field "user", "password_confirmation", :size => 20 %><br/>
<label for="user_email">Email</label><br/>
<%= text_field "user", "email", :size => 20 %><br/>
<%= submit_tag "Zarejestruj się" %>
<%= end_form_tag %>

login.rhtml
<%= start_form_tag :action=> "login" %>
<label for="user_login">Nazwa użytkownika:</label><br/>
<%= text_field "user", "login", :size => 20 %><br/>
<label for="user_password">Hasło:</label><br/>
<%= password_field "user", "password", :size => 20 %><br/>
<%= submit_tag "Zaloguj się" %>
<p>
<%= link_to 'Zarejestruj się', {:action => 'signup'}%> |
<%= link_to 'Zapomniałeś hasła?', {:action => 'forgot_password'}%>
</p>
<%= end_form_tag %>

forgot_passwd.rhtml
<%= start_form_tag :action=>'forgot_password'%>
<h3>Forgotten password</h3>
Email: <%= text_field "user","email" %><br/>
<%= submit_tag 'Prześlij hasło' %>
<%= end_form_tag %>

change_passwd.rhtml
<%= error_messages_for 'user' %>
<h3>Change password</h3>
<%= start_form_tag :action => 'change_password' %>
<label for="user_password">Choose password:</label><br/>
<%= password_field "user", "password", :size => 20, :value=>"" %><br/>
<label for="user_password_confirmation">Confirm password:</label><br/>
<%= password_field "user", "password_confirmation", :size => 20, :value=>"" %><br/>
<%= submit_tag "Zmień" %>
<%= end_form_tag %>

Jeśli chcesz użyć widoku logout.rhtml, możesz w kontrolerze User, przy akcji logout usunąć przekierowanie na akcję login.

Oczywiście, to są tylko przykładowe formularze, możesz je w pełni dostosować do wymogów aplikacji, ważne tylko, żeby wszystkie parametry były przekazywane do funkcji.

5. Użycie
Użycie tego w praktyce jest bardzo proste. Aby się zarejestrować wystarczy wejść na http://twój-adres-serwera/user/singup . Analogicznie wygląda sprawa z logowaniem. Tak samo proste jest ograniczanie dostępu do niektórych akcji lub całego kontrolera – skorzystamy z funkcji login_required:

  • Ograniczenie pojedynczych akcji:
    before_filter :login_required, :only=>['akcja1', 'akcja2', ...]
  • Ograniczenie wszystkich akcji oprócz wybranych:
    before_filter :login_required, :except=>['akcja1', 'akcja2', ...]

Skoro wiemy już jak pozwolić tylko niektórym użytkownikom na dostęp do akcji, to zabezpieczmy nasz kontroler UserController dodając od razu po deklaracji klasy ten kod:

before_filter :login_required, :only=>['change_password']

Możesz chcieć wyświetlać niektóre cześci strony tylko wtedy gdy użytkownik jest zalogowany- można to zrobić takim kodem:
<% if @session[:user]%>
twój kod
<% end %>

6. Co dalej?
W następnej części najprawdopodobniej napiszę o obsłudze ról użytkowników, dodawaniu captchy do rejestracji i może dodam jeszcze coś o potwierdzaniu rejestracji.

Bibliografia: Basic User Authentication in Rails

Tagi:

1 Komentarz do “Obsługa użytkowników w RoR (cz 1)”

  1. mlen napisał:

    Dobry artykuł (choć ja nie koduję w RoR) !

    No i w końcu coś innego niż — ostatnimi czasy tylko — “przegląd sieci” ;-)

    Czekam na kolejną część.

Skomentuj ten wpis!