XSS-Protection in Rails 3

Für mein „altes“ Rails 2 Projekt habe ich mir – zum Schutz vor Cross-Site-Scripting (XSS) Attacken – ein kleines Modul geschrieben, das gefährliche HTML-Tags aus jedem Attribut eines Models mittels h() Methode entfernt. In der neuen Version kann ich darauf komplett verzichten.

In Rails 3 werden standardmäßig alle Ausgaben von unerwünschtem Code gesäubert. Ein einfaches aber sehr wirkungsvolles Feature.

Es kann Fälle geben, bei denen man bewusst auf diese Funktion verzichten will. Hierfür steht die raw() Methode zur Verfügung:

  1. < %= raw article.content %>

Die raw() Methode macht nichts anderes, als dass sie das Attribut html_safe des String-Objekts (bzw. ActiveSupport::SafeBuffer) setzt. Dieses wird beim Rendering des Views abgefragt und entscheidet letztendlich darüber, ob der auszugebende HTML-Code gesäubert wird oder nicht.

In meinen Helper-Methoden ziehe ich es vor auf die raw() Methode zu verzichten und html_safe selbst aufzurufen:

  1. def article_head(article)
  2.   "<h1>#{article.title}</h1>
  3.   <span class='date'>#{article.date}</span>".html_safe
  4. end
Share

How to install ruby-debug on Windows and Ruby 1.9

Heute ein Artikel in Englisch, damit auch die internationale Gemeinde etwas davon hat ;-)

The Rails Debugging Guide told me that I have to install ruby-debug19 instead of ruby-debug when I want to debug on the Ruby 1.9 platform. So I tried to install it on a system which has Ruby 1.9.2 and Windows 7 x64 installed and I ran into trouble:

c:\Railsapps\tuneo>gem install ruby-debug19 --platform=mswin32
Temporarily enhancing PATH to include DevKit...
Building native extensions. This could take a while...
ERROR: Error installing ruby-debug19:
ERROR: Failed to build gem native extension.

C:/Ruby/bin/ruby.exe extconf.rb
checking for vm_core.h... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers. Check the mkmf.log file for more
details. You may need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=C:/Ruby/bin/ruby
--with-ruby-dir
--without-ruby-dir
--with-ruby-include
--without-ruby-include=${ruby-dir}/include
--with-ruby-lib
--without-ruby-lib=${ruby-dir}/lib
C:/Ruby/lib/ruby/1.9.1/mkmf.rb:368:in `try_do': The complier failed to generate
an executable file. (RuntimeError)
You have to install development tools first.
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:452:in `try_cpp'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:834:in `block in have_header'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:693:in `block in checking_for'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:280:in `block (2 levels) in postpone
'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:254:in `open'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:280:in `block in postpone'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:254:in `open'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:276:in `postpone'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:692:in `checking_for'
from C:/Ruby/lib/ruby/1.9.1/mkmf.rb:833:in `have_header'
from extconf.rb:15:in `block in

'
from C:/Ruby/lib/ruby/gems/1.9.1/gems/ruby_core_source-0.1.4/lib/ruby_co
re_source.rb:18:in `call'
from C:/Ruby/lib/ruby/gems/1.9.1/gems/ruby_core_source-0.1.4/lib/ruby_co
re_source.rb:18:in `create_makefile_with_core'
from extconf.rb:20:in `
'

Gem files will remain installed in C:/Ruby/lib/ruby/gems/1.9.1/gems/linecache19-
0.5.11 for inspection.
Results logged to C:/Ruby/lib/ruby/gems/1.9.1/gems/linecache19-0.5.11/ext/trace_
nums/gem_make.out

This is the way I solved it:
1. Start cmd, change to your devkit folder and execute devkitvars.bat
If you have not yet installed the devkit on your system check this Devkit on github.com

2. Execute the gem install command:
gem install ruby-debug19 --platform=mswin32Note: the platform depends on your ruby installtion. To find out which platform you are using, check your ruby version:
c:\Railsapps>ruby -v
ruby 1.9.2p0 (2010-08-18) [i386-mingw32]

After that the ruby debugger is running on the system. Don’t wonder if the execution of the install command takes a while.

Share

Suchmaschinenfreundliche Links mit Ruby on Rails generieren

Standardmäßig sehen in Ruby on Rails generierte Links in etwa so aus:

http://www.example.com/posts/10345

In Hinblick auf die Suchmaschinenfreundlichkeit einer Seite und auch für den Besucher selbst, wäre es aber schöner, wenn die URL z.B. so aussehen würde:

http://www.example.com/posts/10345-Rails-3-ist-fertig

Das Rails-Framework hat dafür einen einfachen wie unkomplizierten Trick auf Lager:

Jedes ActiveRecord Objekt besitzt in Rails eine Methode namens to_param, die standardmäßig den Primär-Schlüssel des Objekts, also die ID, zurück liefert. Um eine suchmaschinenfreundliche URL zu generieren, muss diese Methode überschrieben werden, sodass sie einen entsprechenden String zurück gibt, der den jeweiligen Datensatz eindeutig identifiziert. Für die obige URL müsste das so aussehen:

  1.   def to_param
  2.     "#{id}-#{title.gsub(/[^a-z0-9]+/i, '-')}"
  3.   end

Anschließend kann mit der Methode und dem link_to Helper wie üblich der gewünschte Link erstellt werden:

  1. < %= link_to(@post.title, blog_post(@post.to_param)) %>

Es macht Sinn, sich daran zu gewöhnen, bei der Generierung von Links immer die Methode to_param anstatt der id zu verwenden, auch wenn sie im jeweiligen Model gar nicht überschrieben wurde. Meine Erfahrung hat gezeigt, dass man sich das später manchmal durchaus anders überlegt ;-). Somit erspart man sich ein nachträgliches Anpassen der Views.

Share

RSS-Feed mit Ruby on Rails realisieren

Gerade wurde ich einmal mehr von der Einfachheit und Genialität von Ruby on Rails überrascht. Ich habe soeben meinen ersten RSS-Feed mit Hilfe von RoR erzeugt und das Ergebnis ist so einfach, dass sich eigentlich schon fast jeder Kommentar erübrigt.

Zunächst im Controller die Daten des Models, dass im RSS ausgegeben werden soll, abfragen.

def rss
  1.    @posts = Post.find(:all, :order => 'id DESC', :limit => 20)
  2.    render :layout => false
  3. end

Zu beachten ist, dass (wie oben geschehen) das Rendering des Standard-Layouts im Controller unterbunden werden muss. Anstelle von HTML soll schließlich XML ausgegeben werden. Dazu muss eine Datei namens rss.rxml (man beachte die Dateiendung) erstellt werden, in der ganz einfach mit Hilfe des XML-Objektes die Struktur des RSS-Feeds aufgebaut wird:

xml.instruct! :xml, :version=>“1.0″
  1.  
  2. xml.rss(:version=>"2.0") {
  3.  
  4.   xml.channel {
  5.  
  6.     xml.title("Mein Blog")
  7.     xml.link(root_url)
  8.     xml.description("Infos aus meinem Berufsalltag")
  9.     xml.language("de-de")
  10.  
  11.     @posts.each do |post|
  12.  
  13.       xml.item do
  14.  
  15.         xml.title(post)
  16.         xml.description(post.title)
  17.         xml.pubDate(post.created_at.rfc2822)
  18.         xml.link(post_path(post.to_param))
  19.         xml.guid(post_path(post.to_param))
  20.  
  21.       end
  22.  
  23.     end
  24.  
  25.   }
  26. }

xml.instruct! leitet die XML-Ausgabe ein. Fast alle anderen Methoden, die an das XML Objekt gesendet werden, werden einfach in das gleichnamige XML Markup-Tag „übersetzt“. Methoden mit Blöcken (wie im obigen Beispiel „xml.channel“), werden wie Markup-Tags mit darin eingeschlossenen Tags behandelt.

Die genaue Dokumentation zum XML Builder in Ruby on Rails findet sich hier.

Share

Named Routes in Models und anderen Klassen verwenden

Named Routes in Ruby on Rails sind eine feine Sache. Standardmäßig können sie im Controller und in Views verwendet werden, hier ist ihr Einsatzzweck vorgesehen. In seltenen Fällen kann es jedoch dazu kommen, dass man sie auch in einem Model oder einer anderen Klasse verwenden möchte. Das Erste was man feststellt ist, dass das nicht einfach geht.

Um die Named Routes in der Klasse verfügbar zu machen, muss das entsprechende Modul inkludiert werden:

  1. class App
  2.  
  3.   include ActionController::UrlWriter
  4.  
  5.   def initialize()
  6.     #…
  7.   end
  8. end

Wer lediglich relative Pfade ausgeben möchte, dem wird das genügen. Wer jedoch absolute Pfade mittels *_url Methoden nutzen möchte, der wird auf diese Fehlermeldung stoßen:

Missing host to link to! Please provide :host parameter or set default_url_options[:host]

Das Problem ist, dass der Host, für den die absoluten Pfade erstellt werden sollen, nicht bekannt ist. Dem kann abgeholfen werden, in dem man nach der include Anweisung die entsprechende Variable setzt:

  1. default_url_options[:host] = SITE_HOST

Wie man sieht, verwende ich die Konstante SITE_HOST, die ich in der config-Datei der entsprechenden Environment gepflegt ist. Der Einfachheit wegen, würde ich das auch so weiter empfehlen ;)

Share

Rails Console mit bestimmter Environment laden

Zum Debugging habe ich gerade nach der Möglichkeit gesucht, die Ruby on Rails Console, die standardmäßig in der Development Environment startet, innerhalb der Production Umgebung zu starten.

Die Lösung ist einfach – und war mir trotzdem nicht bekannt ;)

ruby script/console production

Es muss lediglich der Name der Umgebung ohne weitere Parameter angegeben werden.

Share

ActionMailer E-Mail Versand testen

Ich habe soeben nach einer Möglichkeit gesucht, um den Versand einer E-Mail mittels ActionMailer in meinen Functional-Tests zu testen.
Beim Rails-Testing werden die E-Mails nicht real versandt, sondern nur in einer Queue gespeichert. Das ermöglicht es einfach den Inhalt der Queue abzufragen und somit den Versand der E-Mail sicher zu stellen.
Allerdings sollte auch bedacht werden, dass dadurch nur das reine Erstellen der E-Mail geprüft wird. Mögliche SMTP-Fehler beim Versand etc. bleiben dadurch unentdeckt!

  1. test "should send lost password mail" do
  2.  ActionMailer::Base.deliveries.clear
  3.  post(:lost_pwd, { :username => 'calvin', :email => 'calvin@krani.net' })
  4.  assert !ActionMailer::Base.deliveries.empty?    
  5. end

In diesem Beispiel soll geprüft werden, ob nach dem Aufruf der „Lost Password“ Action auch wirklich die E-Mail mit dem neuen Passwort generiert wird.
Hinweis: Es empfiehlt sich genauso wie in dem Beispiel, in jedem Test zunächst die Queue zu leeren, um sicher zu stellen, dass nicht schon E-Mails aus vorhergehenden Tests in der Queue stehen.

Share

Fehler „undefined method use_transactional_fixtures=“ nach Rails Update

Nach dem Update von Ruby on Rails auf die Version 2.3.5 habe ich beim Ausführen meiner Tests plötzlich diesen Fehler erhalten:

./test/test_helper.rb:22: undefined method `use_transactional_fixtures=' for Test::Unit::TestCase:Class (NoMethodError)

Mit Rails 2.3 hat sich der Name für die Klasse der Testhelper geändert. Um das Problem zu lösen, muss daher einfach in der Datei /test/test_helper.rb im jeweiligen Applikations-Verzeichnis der alte Klassenname

Test::Unit::TestCase.

durch

ActiveSupport::TestCase

ersetzt werden.

Share

ActiveRecord: Vorsicht bei der Massenänderung von Attributen

Rails bietet einfache Möglichkeiten um einem Objekt alle benötigten Attribute zuzuweisen. Einem ActiveRecord Model können z.B. schon bei der Instanziierung alle gewünschten Attribute aus den Request-Parametern (z.B. aus einem Formular) übergeben werden. Dieses Vorgehen nennt sich im Rails-Jargon „Mass-assignment“.

Nehmen wir an wir haben ein Model „Comment“, dass die Kommentare von Benutzern einer Webseite speichert. Um Spam vorzubeugen, sollen alle Kommentare erst nach redaktioneller Überprüfung angezeigt werden, weshalb das Model ein Attribut „reviewed“ besitzt. Nur wenn dieses den Wert „1“ hat, wird der Kommentar öffentlich angezeigt.
In der Methode für die Speicherung eines neuen Kommentars steht nun folgender Code:

  1. @comment = Comment.new(params[:comment])

Würde der Anwender den Request nun so manipulieren (z.B. durch das Einfügen eines weiteren Feldes im HTML-Formular), dass dem Host der Parameter params[:comment][:reviewed] mit dem Wert „1“ übergeben wird, wäre die redaktionelle Prüfung umgangen.

Um das zu verhindern, gibt es in ActiveRecord die Methode attr_accessible. Mit dieser kann eine Whitelist der Attribute erstellt werden, die durch Mass-assignment verändert werden dürfen. Die Methode attr_protected bietet die selbe Funktionalität, jedoch mit einem Blacklist-Verfahren, d.h. es werden Attribute definiert, die nicht verändert werden dürfen.

Im Beispiel der Kommentare, müsste man also im Model „Comment“ diesen Code hinterlegen:

  1. attr_protected(:reviewed)

Dann kann das Attribut nur noch mit dem direkten Zugriff manipuliert werden z.B:

  1. comment.reviewed = true

Ich persönlich bevorzuge übrigens das Whitelist-Verfahren, da somit Flüchtigkeitsfehler bei der Entwicklung vermieden werden können ;-)

Share