28
Sep/09
0

Mise en oeuvre de l’authentification (2 - niveau serveur)

L’article précédent a défini la façon dont le code utilisateur sera stocké dans au niveau du poste client, à savoir dans un cookie dont l’altération ne sera pas possible.

Maintenant, il est nécessaire de pouvoir mettre en oeuvre cette formulation au sein de l’application. Cette dernière sera principalement privée.

Récupération des informations

Tout d’abord, l’absence d’un cookie génère un atome undefined qu’il faut être capable de gérer lors de la vérification (fichier src/secure_cookies.erl) :

check_cookie(undefined) ->
  {"Anonymous", ""};
check_cookie(Value) ->
  {UserName, Timeout, Data, CookieMac} = decode(Value),
  ...

Mochiweb permet de récupérer les informations de cookies via la requête. On crée donc une méthode permettant de retrouver l’utilisateur authentifié dans le module context :

find_user(Req) ->
  Cookie = Req:get_cookie_value("app_session"),
  try
    secure_cookies:check_cookie(Cookie)
  catch
    _E -> {"Anonymous", ""}
  end.

Toute erreur de vérification sera transformée en un utilisateur « anonyme » (cette erreur devrait être tracée).

Routage

On modifie donc notre fonction de routage afin de prendre en compte cette caractéristique.

routes(Req) ->
  "/" ++ Path = Req:get(path),
  {User, UserData} = ctx:find_user(Req),
  {Params, RequestData} = ctx:decode(Req),
  case Path of
    "private" ->
      allowed = ctx:authorize(authenticated, User),
      ctrl_test:handle_priv_http(Req, Params, RequestData);
    _ -> Req:respond({501, [], []})
  end.
 
loop(Req, DocRoot) ->
  routes(Req).

On suppose la fonction handle_priv_http/3 existante et retournant une quelconque structure JSON.

Le module de contexte src/ctx.erl fourni la méthode permettant de déterminer si une action peut être déclenchée ou non, selon le niveau d’authentification de l’utilisateur :

authorize(anonymous, _) ->
  allowed;
authorize(_, "Anonymous") ->
  unauthorized;
authorize(authenticated, User) ->
  allowed.
 
test() ->
  allowed = authorize(anonymous, "toto"),
  allowed = authorize(anonymous, "Anonymous"),
  allowed = authorize(authenticated, "toto"),
  unauthorized = authorize(authenticated, "Anonymous").

Ici, l’ordre des fonctions est extrêmement important. En effet, la première fonction permet de vérifier que n’importe quel utilisateur a le droit d’accéder à une partie anonyme du site. Si cette fonction n’est pas déclenchée, c’est que le niveau requis est obligatoirement privé. Donc si un l’utilisateur « anonyme » (ou invité) est détecté à ce moment, c’est qu’il y a un problème.

Si on essaie d’accéder à l’url private, on obtient un joli crash :

=CRASH REPORT==== 
  crasher:
    initial call: mochiweb_socket_server:acceptor_loop/1
    pid: <0.143.0>
    registered_name: []
    exception error: no match of right hand side value unauthorized
      in function  ercm_web:routes/1
      in call from mochiweb_http:headers/5
    ancestors: [ercm_web,ercm_sup,<0.64.0>]
    messages: []
    links: [<0.72.0>,#Port<0.2429>]
    dictionary: [{mochiweb_request_path,"/private"},
                  {mochiweb_request_cookie,[]}]
    trap_exit: false
    status: running
    heap_size: 1597
    stack_size: 24
    reductions: 1254
  neighbours:
 
=ERROR REPORT==== 
{mochiweb_socket_server,235,{child_error,{badmatch,unauthorized}}}

L’utilisateur anonyme n’est pas authorisé.

Gestion d’un problème d’authentification

On va donc récupérer une erreur d’association.

loop(Req, DocRoot) ->
  try
    routes(Req)
  catch
    error:{badmatch,unauthorized} -> Req:respond({403, [], []})
  end.

Attention, si on ne met pas la partie Class: dans le catch (sous la forme Class:Term), seules des exceptions sont trappées et un problème d’association n’est pas intercepté.

Le système prévoit d’interroger le backend Erlang uniquement sous forme de services, on ne prévoit donc que le fait de renvoyer le code HTTP ; il sera de la responsabilité du code javascript de réagir en fonction du code d’erreur.

La fonction de routage reste très basique, elle nous oblige à réécrire les tests d’authentification à chaque nouvelle URL, ce qui n’est pas très DRY.

Comments (0) Trackbacks (0)

No comments yet.

Leave a comment

No trackbacks yet.