Si vous développez vos applications de manière un chouilla propre, vous devez avoir des environnements de développement et de production bien séparés.
Par exemple, l’API RefStats est disponible en production. Mais vous pouvez également visualiser l’interface qui me sers de test en développement.

Pour diverses raisons pratiques, je ne désire pas mettre d’identification sur cette version de développement.
Il n’y a de toute façon pas énormément de risques. La base de données y est remise à zéro particulièrement régulièrement et il est impossible de s’inscrire vu que c’est par le forum que cela se passe.
Par contre étant donné que les pages sont publiques, je ne tiens pas spécialement à ce que les moteurs de recherche ne les indexent.

Pour cela, je désire insérer un robots.txt. Mais uniquement en développement.
Inutile de s’embêter à faire un contrôleur, une action, une vue etc. Je vais utiliser une super fonctionnalité de Rails 2.3 : un metal.

Générons le en ligne de commande dans notre projet :

script/generate metal robots

Ceci nous crée un répertoire app/metal et un fichier robots.rb dedans.
Nous désirons que notre metal surveille l’url « /robots.txt » et que, si on est en développement, il nous retourne son contenu.

Et puisque c’est particulièrement simple, je vous le donne directement :)

require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Robots
    def self.call(env)
        if env["PATH_INFO"] == '/robots.txt'
            [200, {"Content-Type" => "text"}, [ENV['RAILS_ENV'] == 'production' ? '' : "User-agent: *\r\nDisallow: /"]]
        else
            [404, {"Content-Type" => "text"}, [""]]
        end
    end
end

Ainsi dev.api.refstats.net/robots.txt interdit l’indexation.
Tandis que en production, l’indexation est autorisée puisque le robots.txt sera vide (à l’heure ou j’écris ces lignes, l’application n’est pas encore déployée car j’ai pas mal de commit en suspens. Notamment tout ce qui est de la gestion des comptes pro. Donc vous pouvez pas le voir).

Plutôt simple et efficace non ? :)

Quelque soit le langage de programmation que vous utilisez, la génération de PDF est toujours quelque chose d’assez sensible à réaliser. Heureusement il existe des solutions.
En Ruby, j’ai nommé Prawn. Je vais passer rapidement sur la génération de documents avec prawn même puisque j’utilise Rails et Prawnto, qui permet de l’y intégrer de manière à peu près propre (quoi que).

Installons prawn et prawnto.

gem install prawn
gem install prawnto

Avec Prawn, générons donc un document simple, juste pour montrer. Le reste on le fera avec Prawnto (mais de toute façon, le fonctionnement est le même).

Prawn::Document.generate('nom_du_document.pdf') do
    text 'Il est beau mon pdf non ?', :size => 40
end

Qui affichera en gros en haut à gauche de votre document le texte « Il est beau mon pdf non ? »
On a généré notre premier pdf en Ruby. Cool !

Passons à Prawnto.
Ce plugin rails permet de générer des pdf tout en conservant le modèle MVC. Vous spécifiez que vous désirez que telle ou telle vue puisse générer des pdf.
Puis lorsque vous l’appellerez avec params[:format] à ‘pdf’ (sauf exception dans vos routes, en passant .pdf à la fin de l’url), le rendu sera au format pdf.

Commencons donc par déclarer que notre action peut retourner du pdf.

def mon_action
    prawnto :inline => false
end

Puis nous créons un fichier « mon_action.pdf.prawn » dans le répertoire de vues du contrôleur. C’est dans ce fichier que l’on définira la manière de générer notre pdf.

Par exemple, voici, rien que pour vous, un extrait de la génération des factures pour les fonctionnalités payantes qui arrivent bientôt sur RefStats !

pdf.bounding_box [70, 700], :width => 150 do
    pdf.image "app/views/factures/pdf/logo.jpg", :width => 100
end
pdf.bounding_box [300, 670], :width => 150 do
    pdf.text "Receipt", :size => 20
end

pdf.bounding_box [50, 600], :width => 550 do
    pdf.text "User: " + @facture.user.username
    pdf.text "PAID", :size => 30, :align => :center if @facture.paid
end

data = @facture.items.collect { |i| [i.description, i.price, i.quantity, i.total] }
if !@facture.items.empty?
    pdf.bounding_box [0, 400], :width => 550 do
        pdf.table data, :position => :center, :width => 550, :headers => ['Description', 'Price (Taxes Included)', 'Quantity', 'Total Price (Taxes Included)']
    end

    pdf.bounding_box [400, (350-(data.size*20))], :width => 550 do
        pdf.table [['Total', @facture.total]], :position => :right, :width => 150
    end
else
    pdf.text "No items to display (or corrupted receipt)", :at => [200, 400]
end

Quelques explications.
Les bouding boxes permettent de placer des éléments de manière relative dans la page. Ainsi je peux aligner le logo et le texte « Receipt » au même niveau.
Le tableau de chiffres après la définition de la bouding_box indique son emplacement. C’est assez traitre car le premier est l’emplacement par rapport à la gauche du document.
Mais le suivant est l’emplacement par rapport au bas du document.

L’élément table permet de créer un tableau. Si par exemple, votre tableau Ruby ressemble à cela :

[
    [ 'Titre', 'Chiffre' ]
    [ 'Second Titre', 'Second Chiffre' ]
]

Alors le tableau généré sera le suivant :

Titre Chiffre
Second Titre Second Chiffre

Il est possible de placer des éléments en en-tête avec l’option « headers » (voir comment je génère la mienne).
Pour les autres options, elles parlent d’elle même je pense :)

Chargez la page et vous aurez un beau document pdf généré :)
Et vous, vous utilisez quoi pour générer vos pdf (en ruby !)

Lorsque vous définissez, dans une application rails, des url spécifiques avec map.resources, celles-ci finissent de la forme suivante :

/post/1
Ou 1 est l’identifiant de l’uplet dans la base et blog est le nom de la ressource. C’est cool parce que ça permet d’avoir des url assez courtes simplement.
Mais d’un point de vue utilisateur, y’a mieux. Avoir une url du genre de
/post/titre-du-billet
Serait un peu mieux non ? C’est particulièrement simple :)

Map.resources utilise la méthode to_param des modèles. Méthode qui, par défaut, retourne la valeur de l’identifiant.
Si vous la modifiez en placant :

def to_param
    title
end

Ce n’est plus l’identifiant de votre uplet qui sera retourné mais la valeur du champ titre.
A vous donc ensuite d’avoir un titre correct.

Et lorsque vous appellerez la méthode vous permettant d’avoir l’url vers votre billet :

post_url @post

Ce n’est plus /post/1 qui sera retourné mais /post/titre-de-votre-billet.

Bien évidemment la valeur de title doit être correctement formatée pour rendre une url (suppression des accents et espaces etc). Rails ne fait pas cela automatiquement.
Mais ce genre de méthode ne fait pas partie du sujet de cet article. Cela sera pour un autre :)

Regardons un peu les formulaires Django. Et plus particulièrement les ChoiceField.
Avec ceux-ci, nous plaçons une liste déroulante dans notre formulaire.

champ = forms.ChoiceField(choices=((1, 'valeur'), (2, 'seconde_valeur')))

Nous créons ici un ChoiceField dans notre formulaire en y placant également deux valeurs avec les identifiants 1 et 2.

C’est sympa. Mais ça serait mieux de pouvoir récupérer les valeurs de notre base. Gogo alors !

champ = forms.ChoiceField(choices=[[r.id,r.name] for r in Model.objects.all()])

Pour définir les valeurs, nous récupérons toutes les données disponibles avec le modèle « Model » et plaçons les champs id et name. Vous avez donc un ChoiceField dynamique.

Malheureusement (ou heureusement, cela dépends du point de vue), Django place le contenu des formulaires en cache. La requête SQL de récupération de vos modèles ne sera donc executée que si ceux-ci sont modifiés.
Dans un usage basique, cela suffit amplement. Si c’est le cas, je vous invite fortement à conserver cette méthode car ce que j’explique plus bas n’est absolument pas DRY.

Dans mon cas par exemple, j’ai une condition sur mon modèle. Le site est multilingue et je cherche à ne récupérer que les uplets relatifs à la langue en cours.
Avec la mise en cache donc, je n’ai pas les bons éléments car Django ne fait pas particulièrement attention au fait que j’ai changé la requête entre deux (notemment parce que la condition n’est pas dans la requête mais au niveau supérieur, dans le manager).

Je ne peux donc ajouter les éléments directement depuis le formulaire.
Je définis mon champ dans mon formulaire :

champ = model.ChoiceField()

Et dans mon contrôleur (oui c’est pas DRY je l’ai déjà dit) :

if request.method == 'POST':
    form = MyForm(request.POST)
    form.fields['champ'].choices = [[r.id,r.name] for r in Model.objects.all()]
else:
    form = MyForm()
    form.fields['champ'].choices = [[r.id,r.name] for r in Model.objects.all()]

Mon formulaire est correctement défini et avec les bonnes valeurs :)

Et vous, avez-vous déjà eu un problème similaire de cache sur des ChoiceFields de Django ? Avez-vous résolu le problème de manière similaire ou avez-vous réussi à faire quelque chose de plus propre ?

On ne le répètera jamais assez et je ne vous l’apprends théoriquement pas : cryptez les mots de passe de vos utilisateurs en base.
Supposons qu’un hacker découvre une faille de sécurité quelconque sur votre site et que grâce à celle-ci, il puisse obtenir un accès à l’intégralité de votre base (c’est déjà de la grosse faille. Mais mieux vaut être trop prudent).
Vos mots de passe ne sont pas cryptés et tous vos utilisateurs nagent dans leurs excréments (oui oui j’aurais pu dire ça autrement).

Bon du coup on hash (voir md5 ou sha1). Et on crypte pas !
La différence est flagrante : une chaine de caractères hashée n’est pas déhashage.
Une chaine cryptée est décryptable ! Autant dire que le cryptage est, dans notre cas, inutile.

Vous faites déjà perdre au CPU du hacker une bonne semaine de calculs (voir moins si vos utilisateurs ne sont pas des geeks et que leurs mots de passe ne sont donc pas sécurisés).
C’est bien. Mais pas suffisant. Il faut que trouver le mot de passe prenne trop de temps et ne soit pas intéressant à cracker.

Pour cela, il faut les « saler » (urgh la francisation à 2$).
Le principe est simple.

Au lieu de faire :

Digest::MD5.hexdigest 'my secure password'

Vous ajoutez divers éléments dans le hash. J’ai tendance à mettre les éléments suivants :

  • Une chaine aléatoire unique pour chaque utilisateur et stockée dans la base de données.
  • Une chaine aléatoire unique pour tout le site et stockée dans mon fichier de configuration

Le hash est donc généré de la manière suivante :

Digest::MD5.hexdigest user_random_hash + 'my secure password' + site_random_hash

Ainsi pour décoder le hash, il faut non seulement avoir accès à la base de données. Mais également au code source de l’application afin de connaitre le hash du site.
Par ailleurs il est impossible de tester un hash sur tous les utilisateurs. Il faut tester chaque hash indépendament du visiteur. Cela démultiplie donc le temps de calcul de ceux-ci.

Maintenant notre gentil hacker devra donc, pour trouver les mots de passe de vos utilisateurs, avoir accès à :

  • Tous les champs de votre base de données. Et pas uniquement le champ de mot de passe.
  • Le code source de votre application.

Ce qui diminue fortement les risques. Ne les élimine pas évidemment. Mais le risque zéro en sécurité n’existe, de toute façon, pas.
Et vous, comment cryptez-vous les mots de passe de vos utilisateurs ?

Vous l’avez peut-être déjà constaté, je suis rentré de vacances avant hier.
J’ai passé ces vacances en Terre Sainte, visitant les divers endroits d’Israel et de la Palestine. Vous pouvez voir quelques unes des 800 photos que j’ai prises là-bas sur mon pool flickr (à l’heure de la publication de ce billet, je n’ai pas eu le temps de faire le post-traitement sur toutes. Mais ça arrive).

Partant avec un grand groupe (près de 2 000 français), j’ai eu la chance d’assister à deux conférences, une avec un Palestinien lorsque nous étions dans la ville de Bethléem.
Et une avec un Israélien, lorsque nous étions dans la ville de Jérusalem.

Je ne suis pas un expert en relations diplomatiques. Je vais donc tenter d’énoncer les faits tout en restant neutre.
Et je peux zapper certains éléments ou ne pas être au courant d’autres.

Côté Palestinien
Le Palestinien que nous avons eu la chance d’écouter est enseignant à l’université de Bethléem.
Pour lui, le mur de Jérusalem a pour seul but de pousser les Palestiniens à quitter leurs terres (ce que beaucoup font déjà, partant principalement aux USA) et permettre aux Israéliens de récupérer celles-ci.
On sens dans son discours un vrai dépit, une résignation et le manque total d’espoir.

On notera également les propos d’une étudiante en mathématiques qui aurait préféré étudier la physique.
Mais cela est impossible car elle est Palestinienne et cela pourrait lui permettre de construire des bombes.

Côté Israélien
Pour les Israéliens, on sens beaucoup la peur des attentats et une apparente volonté d’arrêter ceux-ci.
Ainsi, enfermer les Palestiniens dans leurs territoires avec le mur de Jérusalem est, pour eux, la seule solution à moyen terme de mettre un terme au conflit.

Le 29 juillet par exemple, les Juifs célébraient la destruction des premier et second temples. Le mur des lamentations était donc plein à craquer.
L’esplanade des mosquées était, quant à elle, fermée car les autorités Israéliennes avaient peur que certains extrêmistes Juifs ne « posent la première pierre du 3e temple » (= ne fassent sauter l’une des mosquées).
Ce qui aurait eu pour effet, bien évidemment, de provoquer la 3e intifada.

On sens d’autant plus cette peur lorsque l’on arrive dans le pays et qu’on en repars.
Mon sac (qui était dans la soute de l’avion) a donc été ouvert.
J’avais un stylo, il a été débouché (au cas ou je ne cache de la poudre dedans ?).
J’avais un duvet et une gourde attachés au dessus du sac, ils ont disparu (je pense qu’ils ont du « oublier » de les rattacher lorsqu’ils ont ouvert le sac).

Et la question à laquelle nous avons tous eu droit en repartant a été « avez-vous été en contact avec des Palestiniens ? Vous ont-ils offert des cadeaux ? »
Aucun cadeau offert. J’ai cependant préféré éviter de mentionner le numéro de téléphone et l’email que l’un des Palestiniens rencontrés à Bethléem m’a fourni.

En conclusion
Il n’y a aucune résolution possible du conflit à court terme. Mais cela, beaucoup s’en seront déjà doutés.
Les Israéliens sont clairement en position dominante mais ils n’ont pas beaucoup plus d’espoir que leurs voisins Palestiniens.

Pour finir on notera une phrase du secrétaire général Israélien délégué au tourisme qui nous a dit, juste avant notre départ « vous avez pu constater comment il est facile de traverser le mur pour aller en Palestine ».
Nous nous posons donc toujours la question de savoir si ce dit passage nous a effectivement été facilité afin de nous pousser à dire du bien d’Israel à notre retour.


Le mur de Jérusalem
(ici, vu côté Israélien).

Comme en mai et juin, voici mes films pour le mois de juillet.
Il y en a un petit peu moins que d’habitude. La raison en est que à l’heure ou vous ce billet est publié, cela fait moins de 24 heures que je suis rentré de vacances et que je rattrape de nombreuses heures de sommeil qui me manquent.

  • Whatever Works
    Note : 8 / 10
    Du grand Woody Allen. Un sujet un chouilla philosophique et beaucoup d’humour détonent pour faire un film fantastique.
  • L’âge de glace III
    Note : 6 / 10
    Difficile de ne pas aimer l’âge de glace, bien que l’on commence à sentir un essouflement dans le thème. On s’amuse encore. Mais il serait temps d’arrêter. Le 4e risque d’être bien moins apprecié.
  • Toyboy
    Note : 3 / 10
    Bien que le film soit en soi un chouilla drole, le scénario est particulièrement branquebalant; des scènes sont particulièrement osées et ce sans raison particulière. Tout cela en fait un film moyen.
    Mais la fin sans aucune morale et totalement dénuée de sens rends le film complètement nul.
  • Public Enemies
    Note : 4 / 10
    Un scénario pas trop mal ficelé. Mais un film beaucoup trop long et parfois creux.
  • Bancs publics
    Note : 7 / 10
    Parfois quelques scènes assez peu réalistes et mal jouées. Mais dans l’ensemble, un film très drole, reflétant de la solitude des grandes villes.
  • Le hérisson
    Note : 8 / 10
    J’avais dit que j’étais prêt à offrir une place pour ce film. Malheureusement, blogueur non influent et été oblige, je n’ai trouvé personne.
    C’est donc areaumbra qui hérite de ma place et pourra aller voir ce magnifique film qui allie aussi bien réflexion sur l’état de notre société actuelle que nous émeut dans les relations qu’il y présente.
  • Harry Potter 6
    Note : 3 / 10
    Je serais JK Rowling, j’aurais vraiment honte de voir ce qu’il aurait été fait de mon histoire. On y zappe tous les moments importants et on amplifie tout ce qui n’est pas important.
    J’espère vraiment que dans 20 ans, un producteur reprenne les livres et les adapte de manière correcte.
 
Fork me on GitHub