Samstag, 25. September 2010

Authentifizierung leicht gemacht mit Devise/Warden

In jeder Anwendung hat man immer wieder das gleiche Problem: Die Nutzer sollen verschiedene Rechte in der Anwendung haben.

Voraussetzung dafür ist zunächst einmal die Registrierung von Nutzern, am besten noch über eine Bestätigungsmail und die Anmeldung der Nutzer über eine Login-Seite.

Bisher hatte ich dafür Authlogic verwendet. Allerdings wurde das zuletzt vor anderthalb Jahren aktualisiert.

Also machte ich mich auf die Suche nach etwas "Modernerem" und stieß auf Devise.

Auf den ersten Blick machte das einen sehr guten Eindruck, obwohl hier anscheinend mit Codegenerierung gearbeitet wird, was Upgrades prinzipiell erschwert.

Allerdings sind die Features so überzeugend, dass ich es einmal ausprobieren wollte. Dazu gehört die Möglichkeit, mehrere Models an die Authentisierung zu koppeln und damit verschiedene Benutzerrollen in der Anwendung verwenden zu können.

Die aktuelle Version 1.1 funktioniert nur mit Rails 3. Da meine Anwendung noch Rails 2.3.8 verwendet, installierte ich also das Gem mit der aktuellen 1.0 Version von Devise:

gem install devise --version 1.0.8

Als nächstes muss Devise in das Rails-Projekt eingebunden werden:

script/generate devise_install

Devise gibt daraufhin ein paar Hinweise aus, was auf jeden Fall manuell angepasst werden muss:
  • Der ActionMailer braucht default_url_options, um die Bestätigungsmais korrekt erzeugen zu können

  • Es muss einen root-Controller geben

  • Im (default-) Layout müssen die Flashes für :notice und :alert vorhanden sein

Damit kann das erste Model für unser Login generiert werden:

thomas$ script/generate devise User
exists app/models/
create app/models/user.rb
exists db/migrate
create db/migrate/20100925163708_devise_create_users.rb
route map.devise_for :users


In der erzeugten Migration und in der User-Klasse kann man nun erst mal die verschiedenen Module aktivieren bzw. deaktivieren und kann dabei auch noch weitere Attribute für das User-Model aufnehmen. Anschließend muss man natürlich die Datenbank per rake db:migrate aktualisieren.

In der User-Klasse sehen die Devise-Module dann so aus:

# Include default devise modules. Others available are:
# :http_authenticatable, :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :activatable
devise :registerable, :database_authenticatable, :recoverable,
:rememberable, :trackable, :validatable


Nun noch in den ApplicationController die Zeile

before_filter :authenticate_user!

einfügen, und schon bekomme ich eine Login-Seite mit der Möglichkeit der Neu-Registrierung und der Zusendung eines vergessenen Passworts an die Mail-Adresse (die im übrigen der default-Login ist).

Die verwendeten Views kommen direkt aus dem gem. Um sie anzupassen, kann man sie ganz einfach in die eigene Anwendung kopieren:

thomas$ script/generate devise_views
exists app/views
create app/views/confirmations
create app/views/confirmations/new.html.erb
create app/views/devise_mailer
create app/views/devise_mailer/confirmation_instructions.html.erb
exists app/views/devise_mailer
create app/views/devise_mailer/reset_password_instructions.html.erb
exists app/views/devise_mailer
create app/views/devise_mailer/unlock_instructions.html.erb
create app/views/passwords
create app/views/passwords/edit.html.erb
exists app/views/passwords
create app/views/passwords/new.html.erb
create app/views/registrations
create app/views/registrations/edit.html.erb
exists app/views/registrations
create app/views/registrations/new.html.erb
create app/views/sessions
create app/views/sessions/new.html.erb
exists app/views/shared
create app/views/shared/_devise_links.erb
create app/views/unlocks
create app/views/unlocks/new.html.erb


Nach dem Einfügen von ein paar CSS-Anweisungen sieht meine Benutzeranmeldung nun schon sehr professionell aus!

Als nächstes will ich ein zweites Model für die Administratoren einführen. Doch das kommt in einem neuen Post....

Samstag, 22. Mai 2010

Deployment: Passenger!

So viel Spaß wie das Entwickeln von Software mit Rails macht, so wenig unterhaltsam war es früher, die Sache dann auch skalierbar an den Mann bzw. auf den Server zu bringen. Rails ist zu heavy für klassisches CGI, FastCGI klemmt dank Schwächen in der Implementierung und der Spezifikation immer an irgendeiner Stelle, mod_ruby taugt nicht für Rails, und die Python-basierten Entwicklungswebserver Webbrick und Mongrel sind nicht wirklich für den Produktiveinsatz gedacht.

Trotzdem galt bis dato ein Reverse Proxy-Setup mit Mongrel noch als das kleinste Übel. Auch dafür ist aber ein ganzer Haufen an Apache-Direktiven notwendig, und das manuelle Einrichten und Kontrollieren der System-Infrastruktur für den Mongrel (Init-Skript, Benutzer etc.pp). Alles keine wirkliche Gaudi, und schnell war das Ergebnis dann auch nicht im eigentlichen Sinne. Richtig pricklig wurde es dann, wenn mehrere Rails-Applikationen auf einem virtuellen Host laufen sollten.

Dagegen ist das Deployment mit mod_passenger die reinste Wohltat. Meine Installation eines Staging-Servers mit zwei Rails-Applikationen auf Ubuntu 8.04 Hary sah im Detail so aus:

  1. Modul installieren: Hardy bringt (im Gegensatz zu Ubuntu 10.04 Lucid und Debian Lenny) selbst noch kein Passenger-Modul mit. Hier gibt es aber freundlicherweise eine Paketquelle mit einem Hardy-Modul, das für mich gut funktioniert hat.
  2. Verzeichnis des Rails-App-Ordners in das Apache-Vhostverzeichnis linken. In meinem Fall dann ln -s /srv/rails/rails-app/public /srv/www/vhost/rails-app.
  3. PassengerRoot /rails-app in die Apache-Vhost-Konfiguration schreiben
    Diesen Schritt kann man sich sogar sparen, wenn die Rails-Applikation direkt im Root eines Virtuellen Hosts laufen soll. Passenger erkennt Rails dann automatisch und richtet alles ein.

  4. fertig! Passenger startet und managet automatisch einen Pool von Rails-Servern, je nach Serverlast, und loadbalanct die Requests.


Auch die Nebenwertungen fallen durchweg positiv aus: Die Fehlermeldungen sind aussagekräftig, inklusiver sinnvoller Tipps zum Lösen von Deploymentproblemen auf den Reporting-Seiten, die mod_passenger anzeigt. Und auch die Dokumentation ist hervorrangend.

Einziger Wermutstropfen momentan: Die Latenz des Staging-Servers liegt bisher mit 250ms deutlich zu hoch, und ein ganzes Stück über unseren sorgsam optimierten Mongrel-Setups. Ich werde drüber berichten, wenn wir da Fortschritte machen. Angeblich ist Performanz-mäßig dieser Tage ja JRuby und ein Java-Applikationserver das gelbe vom Ei...

Also, nicht von der hässlichen Webseite abschrecken lassen, Passenger nehmen und Rails ohne Ärger deployen!

Donnerstag, 20. Mai 2010

Seelenfrieden und Fingerschonung beim HTML-Schreiben: Zen-Coding

Nicht eigentlich ein Rails-Tipp, aber sollte jeder kennen, der gelegentlich die (gemeinhin beschränkte) Freude hat, HTML-Seiten zu editieren: Zen Coding.

Das ist ein ultra-praktische Template-Plugin, das einem beim Schreiben von HTML unglaublich viel Tipparbeit abnimmt. Die Grundidee ist, dass man statt HTML-Tags CSS-Selektoren hinschreibt (also z.B. div.MeineKlasse statt <div class="MeineKlasse"></div>) und dies dann vom Plugin in die eigentlichen Tags expandieren lässt, die man dann nur noch mit Inhalt füllen muss.

Ein paar kleine Beispiele:

div.InfoBereich>span#firstWarning<HOTKEY>

... gibt ...

<div class="InfoBereich">
<span id="firstWarning"></span>
</div>


Das ganze kann man auch weiter treiben, und z.B. Elemente vervielfachen:


div#page>div.logo+ul#navigation>li*5>a<HOTKEY>

.... gibt...

<div id="page">
<div class="logo"></div>
<ul id="navigation">
<li><a href=""></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>
<li><a href=""></a></li>
</ul>
</div>


Außderdem gibts noch praktische Abkürzungen für typische Aufgaben wie "Scheib einen HTML-Header hin".


html:xt

... gibt ...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title></title>
</head>
<body>

</body>
</html>

Für wen gibts das? Praktischerweise für die allermeisten populären Editoren (inklusive vim, textmate, Eclipse, Netbeans).

Ressourcen:

Mittwoch, 19. Mai 2010

Sichere Zufallszahlen mit ActiveSupport::SecureRandom

Auch wenn es manchmal anders scheint: Kaum etwas ist für eine deterministischen Maschine (also einen Computer) so schwierig, wie ein echt "zufälliges" Ergebnis zu erzeugen. Die gleiche Berechnungsvorschrift mit den gleichen Parametern ergibt nun mal per se immer das gleiche Ergebnis.

Weil man häufig mal beim Programmieren aber ein zufälliges Ergebnis braucht, behilft man sich typischerweise mit "Pseudo"-Zufallsgeneratoren. Die basieren auf komplizierten mathematischen Formeln, die ausgehend von einem Startwert deterministisch eine zufällig erscheinende Zahlenfolge erzeugen. Dieser Startwert wird dann beim Programmstart intialisiert, z.B. auf die aktuelle Systemzeit. Dieser "Zufall" ist wohl gut genug, um in iTunes für die zufällige Anordnung einer Playlist zu sorgen, aber es handelt trotzdem um eine deterministische Berechnungsreihe, und zwar eine, deren Startwert relativ leicht erratbar ist (die Systemzeit), das heißt mit ein bisschen Mühe ist Ihr Ergebnis vorhersagbar.

Deswegen sind solche Pseudo-Zufallsgeneratoren für alle sicherheitskritischen Anwendungsfälle absolut ungeeignet. Was also tun? Moderne Betriebssysteme bieten häufig Funktionen an, die "gute" Zufallswerte liefern. Dazu nutzen sie externe Ereignisse als Quelle von "Zufall" -- z.B. Tastendrücke, Netzwerkpakete, Interrupts, die geschickt den generierten Psuedozufallszahlen beigemengt werden. Weil es schwierig ist, alle diese externen Signale genau genug zu beobachten, vorherzusagen, oder nachzuspielen, sinkt damit die Chance rapide, dass ein Angreifer die Zufallszahlen vorhersagen kann.

Leider sind diese modernen Mechanismen aber eben vom konkreten Betriebssystem abhängig, und teilweise schwer zu verwenden. Deshalb ist es praktisch, dass uns Rails diesen Aufwand erspart, indem es uns eine hübsche Bibliothek zur Verfügung stellt: ActiveSupport::SecureRandom. Unter dem Strich lässt sich damit ein Zufallswert, z.B. ein Cookie für eine E-Mail-Bestätigung damit trivial erzeugen:




require 'securerandom'
p SecureRandom.hex(12)
#=> "52750b30ffbc7de3b362"

>> SecureRandom.base64(12)
=> "n7pKog8eliVZm8Pq"


Das ist einfacher und tausend mal besser, als eine SHA1-Summe eines Pseudo-Zufallswerts zu bilden. Also, vor dem Benutzen von Kernel.rand nachdenken, ob die "Zufalls"zahl in irgendeinerweise sicherheitsrelevant ist, und im Zweifelsfall lieber SecureRandom benutzen!