Une nouvelle version beta de Rails3 vient de sortir. Elle utilise une version plus récente d'i18n, ce qui faisait que mon plugin French Rails ne fonctionnait plus. J'ai corrigé ça et sorti une nouvelle version : la 0.2.2.
Suivez moi sur twitter

Aller au contenu | Aller au menu | Aller à la recherche
mercredi 9 juin 2010
Par M'enfin ?!? le mercredi 9 juin 2010, 12:46
Une nouvelle version beta de Rails3 vient de sortir. Elle utilise une version plus récente d'i18n, ce qui faisait que mon plugin French Rails ne fonctionnait plus. J'ai corrigé ça et sorti une nouvelle version : la 0.2.2.
mercredi 23 décembre 2009
Par M'enfin ?!? le mercredi 23 décembre 2009, 00:40
J'ai un petit plugin pour Rails qui permet de localiser facilement une application Rails : FrenchRails. Pour ceux qui auraient raté l'épisode précédent, en gros, ça permet de prendre en compte le fait que 0 est un singulier en français (alors que c'est pluriel en anglais).
Je viens de mettre à jour ce plugin pour qu'il traduise également les 'new' et 'edit' qui se balladent dans les URL générées par Rails. Maintenant, ce sera 'nouveau' et 'modifier', ai-je décidé.
Enfin, tant qu'à resortir ce plugin du grenier, j'en ai également profité
pour faire quelque chose que j'aurais dû faire depuis un certain temps :
le passer en gem. Il est disponible sur http://gemcutter.org/gems/french_rails et peut donc s'installer
d'un simple gem install french_rails.
jeudi 21 mai 2009
Par M'enfin ?!? le jeudi 21 mai 2009, 22:06
Quand je travaille sur des modèles dans Rails, j'ai souvent besoin de
regarder la liste des champs de ce modèle. Vous savez, ces petites questions
toutes bêtes que l'on se pose tous : c'est firstname ou
first_name ? phone ou mobile ?
name ou title ? description ou
body ?
Pour répondre à ces questions, il faut aller chercher dans
db/schema.rb, fichier que l'on a rarement sous les yeux. Mais j'ai
mieux à vous proposer : Annotate. C'est un
gem qui ajoute un commentaire en haut de chacun de vos modèles (et tests
unitaires) avec la déclaration du modèle en question.
Voici un exemple tiré de ma réécriture de LinuxFr.org en Rails :
# == Schema Information
# Schema version: 20090120005239
#
# Table name: news
#
# id :integer(4) not null, primary key
# state :string(255) default("draft"), not null
# title :string(255)
# body :text
# second_part :text
# section_id :integer(4)
# created_at :datetime
# updated_at :datetime
#
Pour l'utiliser, c'est on ne peut plus simple. On fait un gem install
annotate pour l'installer, puis on lance annotate quand on
veut mettre à jour les commentaires. Pour ma part, je fais ça après chaque
rake db:migrate, mais libre à chacun de le lancer quand il le
souhaite.
dimanche 17 mai 2009
Par M'enfin ?!? le dimanche 17 mai 2009, 18:54
Dans la série des outils pour Rails que j'apprécie, je vais vous parler de request-log-analyzer. Pourquoi lui ? Parce qu'il m'a rendu bien service cette semaine.
Request-log-analyser est un outil très simple qui permet d'analyser les logs de Rails pour découvrir les requêtes HTTP qui consomment du temps CPU. En pratique, ça s'installe simplement avec gem, puis on le lance pour générer un rapport (j'aime bien la version HTML) :
gem install request-log-analyzer request-log-analyzer --output HTML --file report.html log/production.log firefox report.html
Le rapport nous fournit un certain nombres d'informations que les hits sur chaque page, les codes HTTP renvoyés, les requêtes les plus lourdes. Pour ma part, je me sers surtout du tableau des 20 requêtes HTTP les plus longues en temps cumulé (Request duration - top 20 by cumulative time). Cela me donne une liste d'actions à bencher pour lesquelles une optimisation est toujours bonne à prendre. Je passe également un peu de temps à regarder si ces requêtes apparaissent dans les 20 requêtes les plus lourdes pour la base et les 20 requêtes avec le temps de rendering le plus long (toujours en temps cumulé). Je peux ainsi avoir une idée de ce qui prend du temps, et de valider ainsi que les résultats du bench collent avec ça. Si je vois qu'une requête HTTP passe beaucoup de temps sur la base de données, mais que les résultats du bench ne montrent pas ça, je vais probablement importer la base de données du serveur de production et l'utiliser pour refaire les benchs.
Un autre tableau intéressant est la liste des requêtes bloquantes (Process blockers (> 1 sec duration)). Si je vois des requêtes faire pas mal de hits dans cette liste, je sais qu'il va falloir les surveiller de près.
Voilà, request-log-analyzer n'est pas un outil magique. Il ne fait qu'une chose, mais il le fait bien. Et c'est très utile pour savoir par où commencer à optimiser un site.
lundi 4 mai 2009
Par M'enfin ?!? le lundi 4 mai 2009, 00:06
Je commence une série d'articles sur des outils qu'il m'arrive d'utiliser quand je fais du développement Rails et qui mériterait, à mon avis, d'être plus connu. Le premier article de la série est Railroad.
Railroad est un script Ruby qui permet de générer des graphes à partir d'une application d'une Rails, ce qui peut être très pratique pour accompagner une documentation quand on a la flemme de faire ces diagrammes soi-même. Railroad permet de générer 3 types de graphes : un pour les modèles, un pour les controlleurs et un pour les machines à états d'acts_as_state_machine. Les diagrammes sont générés au format .dot, ce qui permet d'en faire facilement des .png ou des .svg avec la suite graphviz.
L'auteur du script original ne maintient plus ce script mais on peut en trouver des forks sur Github. Je conseille la branche de David Dollar : elle fonctionne avec les dernières versions de Rails et comporte mes patchs (1 2 et 3).
En pratique, on installe le gem ddollar-railroad, on ajoute une
ligne à son fichier Rakefile et roulez jeunesse :
gem install ddollar-railroad echo "require 'railroad/tasks/diagrams' if RAILS_ENV == 'development'" >> Rakefile rake doc:diagrams
On obtient 3 diagrammes dans doc/diagrams (à condition que graphviz soit installé). Voici, par exemple, ceux que j'obtiens pour la version Rails de LinuxFr.org :
mercredi 18 mars 2009
Par M'enfin ?!? le mercredi 18 mars 2009, 01:50
Comme vous le savez déjà sûrement, je suis en train de re-écrire LinuxFr.org en Rails. Et quand j'ai montré où j'en étais, on m'a remonté un bug étrange : j'affiche "0 commentaires" avec un s à la fin, ce qui est grammaticalement incorrect.
<interruption culturelle> Pour ceux qui ne sont pas très calés en internationalisation, sachez que les règles qui définissent singulier et pluriel ne sont pas les mêmes selon les langues (en fait, certaines langues ont mêmes plusieurs sortes de pluriels). En particulier, il y a une différence importante entre le français et l'anglais : en français, 0 est singulier, alors qu'il est pluriel en anglais. Ruby on Rails utilisant par défaut l'anglais, on comprend mieux d'où vient le 's' à la fin de "0 commentaires". </interruption culturelle>
J'utilise l'helper pluralize, et comme Rails a intégré une
gestion de l'internationalisation à la version 2.2, je pensais qu'il suffirait
de déclarer la locale pour que cela marche. Hé bien, non. Première
surprise : l'helper pluralize ne passe pas par la partie
I18n, mais utilise une règle en dure pour savoir si un nombre est singulier ou
pluriel. Bon, ce n'est pas grave, ce n'est pas ça qui va m'arrêter : un
petit monkey-patching et c'est réglé.
Sauf que, deuxième surprise, cela ne marche toujours pas ! Le backend d'I18n fourni avec Rails (I18n::Backend::Simple) ne connaît que la règle pour l'anglais, et ne tient donc pas compte de la locale. Après quelques errements et expérimentations, j'ai réussi à trouver un moyen relativement simple de corriger cela (créer un backend qui hérite de I18n::Backend::Simple, avec juste la méthode pluralize redéfinie). Et là, joie, ça marche :-)
Comme tout cela m'a pris quelques heures, j'en ai fait un plugin : FrenchRails. J'espère que cela pourra servir à d'autres personnes. En tout cas, moi, je compte l'utiliser sur plusieurs projets.
Dernière chose : si vous avez des besoins plus compliqués que les miens (au hasard, gérer plusieurs langues), ne cherchez pas à utiliser ce plugin, parter plutôt sur une solution plus costaud comme Globalize2.
Mise à jour : FrenchRails est maintenant disponible en gem.
vendredi 18 juillet 2008
Par M'enfin ?!? le vendredi 18 juillet 2008, 23:45
La sécurité des applications web est souvent un sujet délicat : peu de temps à y consacrer, mais cela peut avoir des conséquences assez graves. Pas de panique ! Ruby on Rails est bien armé et avec un peu de rigueur, on peut se protéger sans trop de difficultés. Nous allons voir les principaux types d'attaques et comment les éviter.
Commençons par un grand classique : les injections SQL. Une injection SQL consiste simplement à envoyer des données non prévues dans une requête SQL. Prenons comme exemple une application web où les utilisateurs sont authentifiés de la manière suivante :
@current_user = User.find(:first, :conditions => "login='#{params[:login]}' AND password='#{params[:password]}'")
En temps normal, quand Joe s'authentifie, la requête SQL suivante est exécutée :
SELECT * FROM users WHERE login='Joe' AND password='0521bc575b0ff61daa62494c7ae9c5b6' LIMIT 1;
Mais supposons maintenant que Kevin, un Script Kiddie, passe dans le coin et
décide de mettre "Joe'; --" dans le champ login. La requête SQL va
alors ressembler à :
SELECT * FROM users WHERE login='Joe'; --' AND password='00000000000000000000000000000000' LIMIT 1;
Kevin a réussi à se faire passer pour Joe sans connaître son mot de passe !
Heureusement, Active Record permet de nous en protéger assez facilement. Pour cela, il suffit d'utiliser les formes échappées comme cela :
@current_user = User.find(:first, :conditions => ["login=? AND password=?", params[:login], params[:password]])
ou de façon équivalente :
@current_user = User.find(:first, :conditions => {:login => params[:login], :password => params[:password]})
Dans les 2 cas, Active Record rajoutera un caractère '\' devant
chaque apostrophe de façon à éviter les injections SQL.
Pour la grande majorité des projets, l'authentification (et la gestion des droits qui vont avec) est un passage obligé. Pour cela, il existe un certain nombre de points importants à respecter comme le chiffrement des mots de passe stockés en base de données (ce que nous n'avons pas fait dans l'exemple précédent). Les erreurs sont vite arrivées, aussi je vous recommande d'utiliser des plugins reconnus comme Restful Authentication, OpenID Authentication et Authorization.
Il ne vous reste plus qu'à faire attention à un dernier détail : mettre en cache des pages nécessitant une authentification est une mauvaise idée. En effet, ces pages vont alors être servies par le serveur web sans passer Rails, et donc sans vérification de l'authentification.
L'étape suivante consiste à bien sécuriser l'accès aux données, aussi bien en lecture qu'en écriture. En effet, Rails possède quelques raccourcis très pratiques, mais qui peuvent poser problème quand ils sont mal maîtrisés. Le plus courant est l'affectation de masse, technique qui consiste à créer un objet Active Record directement depuis les paramètres de la requête HTTP. Par exemple, la création d'un compte utilisateur pourra s'effectuer de la façon suivante :
@user = User.create(params[:user])
Supposons maintenant que la table 'users' comporte un champ
'admin' qui vaut 0 par défaut ou 1 pour
les super-utilisateurs. Un utilisateur malveillant pourrait forger la requête
HTTP pour ajouter un paramètre useradmin=1 afin de gagner les pouvoirs réservés aux admins. La
première solution pour se protéger de cette attaque consiste à écrire
explicitement quels sont les paramètres autorisés :
@user = User.create(
:login => params[:user][:login],
:email => params[:user][:email],
:password => params[:user][:password],
:cgu => params[:user][:cgu])
Mais ceci peut vite devenir pénible quand on commence à avoir des
formulaires un peu conséquents. C'est pourquoi on lui préfère généralement la
deuxième solution : la déclaration dans le modèle de la liste des
attributs qui ne peuvent pas être modifiés. Cette déclaration se fait à l'aide
de la méthode attr_protected comme
suit :
class User < ActiveRecord::Base
attr_protected :admin
...
end
Nous pouvons de nouveau utiliser l'affectation de masse sans craindre qu'un utilisateur se fasse passer pour un admin, Rails s'occupe de filtrer les paramètres.
Dans le même style, un attaquant peut essayer de forger
des URL. Si, par exemple, l'utilisateur authentifié peut supprimer l'item
n°123 qui lui appartient, en appelant l'URL /items/delete/123,
alors que se passera-t-il s'il appelle la même URL pour l'item n°456 qui ne lui
appartient pas ? La réponse dépend du code de la méthode
delete. Une implémentation de base pourrait ressembler
à :
class ItemsController < ApplicationController
def delete
Item.delete(params[:id])
end
end
Pour se protéger des URL forgées, on pourrait la transformer en :
class ItemsController < ApplicationController
def delete
@item = @current_user.items.find(params[:id])
@item.delete if @item
end
end
Ce n'est pas parfait (on pourrait vérifier que c'est bien une requête de type POST), mais c'est déjà beaucoup mieux.
Un dernier petit truc pour la route avant de passer à autre chose. Si vous
avez une API pour laquelle vous utilisez la sérialisation XML, il peut être
intéressant de surcharger ActiveRecord#to_xml pour que le champ
secret_field n'y apparaisse pas :
class Item < ActiveRecord::Base
def to_xml(args={})
super({:except => [:secret_field]}.merge(args))
end
end
Jusque maintenant, nous avons vu des attaques directes : un utilisateur
essaye de s'en prendre à notre site. Il existe également des attaques plus
pernicieuses que l'on classe sous le nom de Cross-Site Scripting
(XSS en abrégé). Leur but est de s'en prendre aux utilisateurs de notre site en
glissant des cochonneries sur notre site. Ceci peut aller du spammeur qui
mettra une balise <iframe> vers son site dans tous les
formulaires qui lui passent sous la main à l'injection de javascript non
maîtrisé.
Par exemple, quelqu'un crée un item dont la description est la suivante :
<script>document.location='http://www.programmez.com/';</script>
Si maintenant un visiteur affiche la description de cet item, il sera redirigé vers le site www.programmez.com. Vous vous dites que c'est ennuyeux mais pas bien méchant ? Oui, mais la même technique permet de voler les cookies et donc les sessions associées. Nous allons donc chercher à nous protéger de ces failles XSS.
Pour cela, il est important de faire une distinction entre 2 cas :
est-ce que le champ que vous allez afficher peut contenir des balises HTML ou
non ? Pour afficher le nom d'un item, on sera dans le premier cas, à
savoir pas de balises HTML : on veut juste afficher le nom tel que l'a
rentré son propriétaire. Par contre, on peut souhaiter être plus souple pour la
description de l'item et laisser la possibilité d'avoir un titre (balise
<h1>), du gras (<b>) ou de l'italique
(<i>). Ces 2 cas ne se traitent pas de la même façon. Pour
le premier cas, Rails nous offre un moyen simple de nous en protéger : le
helper h. En pratique, à chaque fois que l'on
souhaitera afficher le titre d'un item, on procédera de la manière
suivante :
<%=h @item.title %>
Ce h va convertir les caractères qui pourraient être
interprétés par un navigateur web en l'entité HTML correspondante. Problème
résolu.
Le deuxième cas est par contre plus difficile à traiter. Vous pouvez être tenté d'utiliser un moteur de formatage de texte comme RedCloth. Attention, cela ne suffit pas à filtrer toutes les attaques ! Pour votre tranquillité, il vaut mieux utiliser le plugin WhiteList. Depuis Rails 2.0, ce plugin fait partie du framework et peut s'utiliser de la façon suivante :
<%= sanitize @item.description, :tags => %w(b i h1) %>
Il est possible de déclarer les balises autorisées de manière globale :
je vous renvoie à la
documentation officielle. Et pour ceux qui veulent être sûrs de ne pas
oublier d'appel à h ou à sanitize, il existe des
moteurs de template alternatifs comme Safe ERB ou Erubis. Ces moteurs adoptent l'approche
opposée : ils filtrent par défaut tous les éléments <%=
%>, charge au développeur d'indiquer explicitement ceux pour lequel
le moteur ne fera pas de filtrage.
Juste avant de finir, je voudrais juste dire un mot sur un dernier type d'attaques. Les CSRF, abréviation de Cross-Site Request Forgery, sont des attaques complexes qui visent à forcer l'utilisateur à envoyer une requête HTTP vers notre site lorsque celui-ci visitera le site de l'attaquant. Je vous renvoie à wikipedia si vous voulez comprendre comment fonctionne ce type d'attaques. Sachez que Rails vous protège de celles-ci depuis la version 2.0 et qu'il existe un plugin pour les versions plus anciennes : CSRF-killer.
Nous avons pu voir qu'en prenant quelques bonnes habitudes, on pouvait développer des applications sûres en Rails. Il reste cependant des sujets que je n'ai pas abordés comme l'administration de Rails, apache ou MySQL (suite), la manipulation des fichiers ou encore le filtrage des informations sensibles dans les logs. Il est également important de se tenir au courant des mises à jour de sécurité de Rails et des plugins que vous utilisez.
Je remercie Benoît Sibaud pour sa relecture attentive.
jeudi 8 mai 2008
Par M'enfin ?!? le jeudi 8 mai 2008, 17:37
La RailsConf 2008, la grande messe annuelle des railers, aura lieu du 29 mai au 6 juin. A cette occasion, un certain nombre d'annonces devrait être faites. Bien entendu, la plus attendue sera celle de Ruby on Rails 2.1. Mais c'est loin d'être la seule. Coté framework, on devrait également entendre parler de Merb dont la version 1.0 devrait être annoncée par ce RailsConf si tout va bien.
On attend également beaucoup de choses pour les interpréteurs Ruby : arrivée du tout nouveau MagLev, Rubinius faisant tourner des applis Rails, JRuby plus rapide que l'interpréteur Ruby officiel pour les applis Rails ? Pour le moment, ce ne sont que des objectifs (ou au mieux des rumeurs), mais on sent une certaine agitation de ce coté-là.
Enfin, la mode est au moteur de réseaux sociaux, et le RailsConf sera l'endroit pour en parler, mais ceci fera l'objet d'un autre post sur ce blog ;-) Bref, plein de bonnes choses en vue qui me feront regretter de ne pas pouvoir y aller...