Follow me
RSS feed
My sources
My Viadeo

Authentification HTTP avec Capcode

Greg | 09 Oct 2009

projetsQuand 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 require 'capcode'
 2 require 'rack'
 3  
 4 module Capcode
 5   class Private < Route '/private'    
 6     def get
 7       # Basic HTTP Authentication
 8       http_authentication( :type => :basic, :realm => "My Capcode test!!!" ) { 
 9         {
10           "greg" => "toto",
11           "mu" => "maia"
12         }
13       }
14       render "Success !"
15     end
16   end 
17   
18   class Public < Route '/public'
19     def get
20       render "You don't need any special authorization here !"
21     end
22   end
23 end
24  
25 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 :

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 :

# 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 require 'capcode'
 2 
 3 module Capcode
 4   class Public < Route '/public'
 5     def get
 6       render "You don't need any special authorization here !"
 7     end
 8   end
 9   
10   class Private < Route '/private'
11     def get
12       render "Welcome to the private part of this site !"
13     end
14   end
15 
16   class PrivateAgain < Route '/private/again'
17     def get
18       render "Welcome to the private/again part of this site !"
19     end
20   end
21   
22 end
23 
24 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 require 'capcode'
 2 require 'digest/md5'
 3 
 4 module Capcode
 5 
 6   OPAQUE = Digest::MD5.hexdigest( Time.now.to_s )
 7   http_authentication( :type => :digest, :opaque => OPAQUE, :realm => "Private parts", :routes => "/private" ) { 
 8     {
 9       "greg" => "toto",
10       "mu" => "maia"
11     }
12   }
13 
14   class Public < Route '/public'
15     def get
16       render "You don't need any special authorization here !"
17     end
18   end
19   
20   class Private < Route '/private'
21     def get
22       render "Welcome to the private part of this site !"
23     end
24   end
25 
26   class PrivateAgain < Route '/private/again'
27     def get
28       render "Welcome to the private/again part of this site !"
29     end
30   end
31   
32 end
33 
34 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.

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.