Authentification HTTP avec Capcode
Quand j’ai mis en place le moteur de rendu WebDAV dans Capcode, la première remarque qui m’est revenue fut « Oui, mais comment faire pour sécuriser un peu tout ça ?« . Bonne question dont malheureusement la réponse fut beaucoup plus facile à mettre en place que ce que j’avais imaginé1.
La sécurisation d’un serveur WebDAV passe par de l’authentification HTTP. Il existe une solution dans rack, via Rack::Auth. Malheureusement, non seulement la documentation est un élément qui fait cruellement défaut dans rack, mais en plus s’il est facile de mettre en place une authentification globale pour toute une application, c’est une autre paire de manches quand on souhaite faire cela pour une route précise. En effet, il n’est pas rare d’avoir, dans un site, une partie publique et une autre privée. Et c’est bien ce que je souhaitais permettre.
En cherchant sur Google, je suis tombé sur cet excellent post expliquant comment mettre en place une authentification Basic dans Sinatra. J’ai donc adapté la solution pour Capcode. Le problème de l’authentification Basic c’est qu’elle est plutôt réservée aux sites accessibles en HTTPS puisque les mots de passent sont en clair dans l’entête. J’ai donc fait un petit passage par la RFC26172 et en lisant attentivement le code de Rack::Auth::Digest::Request et Rack::Auth::Digest::MD5, j’ai rapidement ajouté l’authentification Digest. Ceci a donné naissance à un helper pour les controôleurs : http_authentication.
La mise en place est donc très simple. Voici un petit exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | require 'capcode' require 'rack' module Capcode class Private < Route '/private' def get # Basic HTTP Authentication http_authentication( :type => :basic, :realm => "My Capcode test!!!" ) { { "greg" => "toto", "mu" => "maia" } } render "Success !" end end class Public < Route '/public' def get render "You don't need any special authorization here !" end end end Capcode.run( ) |
La partie importante se trouve de la ligne 8 à la ligne 13.
http_authentication permet de spécifier que vous voulez mettre en place une authentification HTTP pour le contrôleur Private. Ce helper supporte les options suivantes :
- :type qui permet de préciser le type d’authentification. Les valeurs possibles sont :basic (valeur par défaut) et :digest.
- :realm qui donne l’information sur le nom et mot de passe que peut utiliser l’utilisateur. C’est une chaine de caractère pouvant prendre n’importe quelle valeur (« Capcode.app » par défaut).
- :opaque qui est une chaîne générée par le serveur devant être retournée telle quelle par le client (defaut : « opaque »). Cette option n’est utile que dans le cas d’une authentification Digest.
Enfin, http_authentication prend en paramètre un bloc qui doit lui retourner un hachage contenant les couples login/mot de passe pouvant s’authentifier, sous la forme :
{ "user1" => "pass1", "user2" => "pass2", # ... }
Dans l’exemple ci-dessus, nous avons mis en place une authentification Basic. Aux vues de ce qui vient d’être dit, vous voyez aisément comment passer à une authentification de type Digest :
7 8 9 10 11 12 13 | # Digest HTTP Authentication http_authentication( :type => :digest, :opaque => "Ma phrase secrète", :realm => "My Capcode test!!!" ) { { "greg" => "toto", "mu" => "maia" } } |
Tout cela c’est très bien me direz vous, mais comment je fais si je veux protéger plusieurs routes avec une même authentification. Prenons l’exemple suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | require 'capcode' module Capcode class Public < Route '/public' def get render "You don't need any special authorization here !" end end class Private < Route '/private' def get render "Welcome to the private part of this site !" end end class PrivateAgain < Route '/private/again' def get render "Welcome to the private/again part of this site !" end end end Capcode.run( ) |
Avec ce qui a été dit ci-dessus, si nous voulons protéger les parties Private et PrivateAgain, nous devons utiliser deux fois http_authentication. Essayez de le mettre seulement dans Private et vous verrez que vous pouvez accéder à PrivateAgain sans aucun problème. Ce qui est un problème !!!
Heureusement, il y a une solution. En effet, vous pouvez faire une déclaration de demande d’authentification de manière globale. Pour cela j’ai mis en place une méthode Capcode::http_authentication qui ressemble en tout point au helper utilisable dans les contrôleurs, mais prenant un paramètre de plus : :routes. Ainsi, vous pouvez mettre en place la protection souhaitée de la façon suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | require 'capcode' require 'digest/md5' module Capcode OPAQUE = Digest::MD5.hexdigest( Time.now.to_s ) http_authentication( :type => :digest, :opaque => OPAQUE, :realm => "Private parts", :routes => "/private" ) { { "greg" => "toto", "mu" => "maia" } } class Public < Route '/public' def get render "You don't need any special authorization here !" end end class Private < Route '/private' def get render "Welcome to the private part of this site !" end end class PrivateAgain < Route '/private/again' def get render "Welcome to the private/again part of this site !" end end end Capcode.run( ) |
A la ligne 7, nous utilisons la méthode http_authentication pour dire que nous voulons une protection pour toutes les routes ayant pour racine /private. En terme d’expression régulière cela veut dire que si la route match avec /^\/private([\/]{1}.*)?$ alors il faudra s’authentifier pour y accéder.
Notez également que le paramètre :routes peut prendre comme valeur un tableau, ce qui peut grandement nous faciliter la vie si nous voulons utiliser une protection commune pour des routes n’ayant pas la même racine.
Et notre serveur WebDAV alors ? Et bien voici sa nouvelle version :
# file: sample.rb require 'rubygems' require 'capcode' require 'capcode/render/webdav' module Capcode # !!! Render file from /Users/greg/Documents/etudes !!! class WebDav < Route '/etudes' def get http_authentication( :type => :digest, :realm => "Greg' WebDAV Server" ) { { "greg" => "my super secret password", "commercial" => "semaine 23" } } render :webdav => "/Users/greg/Documents" end def method_missing(id, *a, &b); get; end end end
Dernière petite chose avant de terminer, sachez que vous pouvez récupérer le login utilisé via request.env['REMOTE_USER'].
1 bon, j’ai un jour de retard, mais quand même !!!
2 Lire également l’article HTTP Authentification de Wikipedia.

[...] This post was Twitted by glejeune [...]