Code source de note.nk

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

"""Utilitaires de communication avec le serveur NK2015"""

import json
import socket
import ssl
import urllib
import re
import time
import os.path

# Les objets de réponse HTTP
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render

# Les paramètres django
import settings

# Les messages
import messages

#: Ce module contient les valeurs qu'on a besoin de conserver
#: d'une requête HTTP du client à une autre
import keep_alive

import basic


[docs]class NKError(Exception): """Classe de base d'une erreur survenant pendant la communication avec le serveur NK2015. """ def __init__(self, msg): Exception.__init__(self) self.msg = msg def __str__(self): return str(self.msg) def __unicode__(self): return unicode(self.msg)
[docs]class NKRefused(NKError): """Levée en cas de connection refused.""" pass
[docs]class NKHelloFailed(NKError): """Levée en cas d'échec au hello.""" pass
[docs]class NKUnknownError(NKError): """Levée en cas d'autre erreur.""" pass
[docs]class NKDeadServer(NKError): """Levée quand le serveur ne répond plus.""" pass
[docs]class NKNotJson(NKError): """Levée quand le message transmis n'est pas un JSON.""" pass
[docs]def full_read(socket): """Lit un message complet sur la socket.""" # On récupère d'abord la taille du message length_str = '' char = socket.recv(1) while char != '\n': length_str += char char = socket.recv(1) total = int(length_str) # On utilise une memoryview pour recevoir les données chunk par chunk efficacement view = memoryview(bytearray(total)) next_offset = 0 while total - next_offset > 0: recv_size = socket.recv_into(view[next_offset:], total - next_offset) next_offset += recv_size try: msg = json.loads(view.tobytes()) except (TypeError, ValueError) as e: raise NKNotJson("L'objet reçu n'est pas un JSON") return msg
[docs]def _is_success_code(cod): """Dit si un code de retour est un succès ou non""" return cod == 0 or 100 <= cod < 200
[docs]def connect_NK(request): """Connecte une socket au servuer NK2015 et la renvoie après avoir effectué le hello. ``ip_user`` est l'IP de l'utilisateur que django va transmettre au backend. Lève une erreur en cas d'échec""" # On récupère l'IP de l'utilisateur ip_user = basic.get_client_ip(request) sock = socket.socket() try: # On établit la connexion sur port 4242 sock.connect((settings.NK2015_IP, settings.NK2015_PORT)) # On passe en SSL ca_file = os.path.join(settings.PROJECT_PATH, "keys/ca.crt") sock = ssl.wrap_socket(sock, ca_certs=ca_file) # On fait un hello sock.write(json.dumps(["hello", ["HTTP Django", ip_user]])) # On récupère la réponse du hello out = full_read(sock) except Exception as exc: # Erreur de connexion plus explicite en mode debug if settings.DEBUG: raise # Si on a foiré quelque part, c'est que le serveur est down raise NKRefused(str(exc)) if out["retcode"] == 0: return sock elif out["retcode"] == 11: raise NKHelloFailed(out["errmsg"]) else: raise NKUnknownError(out["errmsg"])
[docs]def _gen_redirect_postlogin(request): """Génère l'uri de redirection contenant ``"?next=<la page où on veut aller après le login>"``""" next_page = re.sub(r"\?.*", "", request.path) # On ne garde pas ce qui était éventuellement présent dans le get. return settings.NOTE_LOGIN_URL + "?%s" % (urllib.urlencode({"next" : next_page}),)
[docs]def gerer_NKError(request, exc): """Fait ce qu'il faut en fonction de l'erreur qui a eu lieu pendant la communication avec le serveur NK2015.""" if isinstance(exc, NKRefused): messages.add_error(request, messages.ERRMSG_NK2015_DOWN) return HttpResponseRedirect(_gen_redirect_postlogin(request)) if isinstance(exc, NKHelloFailed): messages.add_error(request, messages.ERRMSG_HELLO_FAILED) return HttpResponseRedirect(_gen_redirect_postlogin(request)) if isinstance(exc, NKDeadServer): messages.add_error(request, messages.ERRMSG_NK2015_NOT_RESPONDING) return HttpResponseRedirect(_gen_redirect_postlogin(request)) if isinstance(exc, NKUnknownError): erreur = messages.ERRMSG_UNKOWNERROR + "\n" erreur += str(exc) messages.add_error(request, erreur) return HttpResponseRedirect(_gen_redirect_postlogin(request)) else: typ = django.utils.html.escape(str(type(exc))) s = django.utils.html.escape(str(exc)) return HttpResponse("La gestion de cette erreur n'est pas prévue :\n%s : %s" % (typ, s))
[docs]def login_NK(request, username, password, form, masque=[[], [], False]): """Ouvre une connexion au serveur NK2015 par username/password Renvoie dans tous les cas un objet HttpResponse[Redirect] utilisable directement""" try: sock = connect_NK(request) data = [username, password, "bdd", masque] paquet = ["login", data] sock.write(json.dumps(paquet)) out = full_read(sock) retcode, errmsg = out["retcode"], out["errmsg"] except NKError as exc: return gerer_NKError(request, exc) if retcode == 0: # login réussi request.session["logged"] = "ok" # On demande au serveur quelles sont les pages auxquelles on a le droit d'accéder try: sock.write(json.dumps(["django_get_accessible_pages", settings.EXISTING_PAGES])) out = full_read(sock) if _is_success_code(out["retcode"]): pages = [i for i in out["msg"] if i[0]!="Index"] # On ignore la page d'index, inutile car le lien est déjà là else: messages.add_error(request, out["errmsg"]) return HttpResponseRedirect(settings.NOTE_LOGIN_URL) except NKError as exc: return gerer_NKError(request, exc) save_pages = [] for p in pages: save_pages.append({ "name": p[0], "link": p[1], "full_link": "%s%s/" % (settings.NOTE_ROOT_URL, p[1]), }) request.session["pages"] = save_pages sock.write(json.dumps(["whoami"])) out = full_read(sock) whoami = out["msg"] request.session["whoami"] = whoami # On conserve la connexion au serveur NK2015 keep_alive.CONNS[whoami["idbde"]] = sock # On redirige vers /index, sauf si on était en train de se reloguer en venant d'un endroit particulier index_fallback = '%sindex/' % (settings.NOTE_ROOT_URL,) next = request.GET.get("next", index_fallback) return HttpResponseRedirect(next) else: messages.add_error(request, errmsg) try: del request.session["logged"] except: # Si on ne s'est encore jamais logué, la valeur n'existe pas pass variables = basic._fundamental_variables() variables["form"] = form return render(request, 'note/login.html', variables)
[docs]def socket_still_alive(request): """ Récupère dans :py:mod:`keep_alive` la socket de communication avec le serveur NK2015 et vérifie que la session est toujours active. * En cas de réussite, renvoie ``(True, <la socket de connexion>)``. * En cas d'échec, renvoie ``(False, <un objet HttpResponse prêt à l'emploi>)``. """ idbde = request.session["whoami"]["idbde"] # On récupère la socket dans keep_alive.CONNS if keep_alive.CONNS.has_key(idbde): sock = keep_alive.CONNS[idbde] else: messages.add_error(request, messages.ERRMSG_NOSOCKET) return (False, HttpResponseRedirect(_gen_redirect_postlogin(request))) # On vérifie que le serveur NK2015 est toujours d'accord pour nous parler try: sock.write(json.dumps(["mayi", "alive"])) out = full_read(sock) except NKError as exc: return (False, gerer_NKError(request, exc)) retcode, still_alive = out["retcode"], out["msg"] if retcode == 0: if still_alive: return (True, sock) else: messages.add_error(request, messages.ERRMSG_NK2015_SESSION_EXPIRED) else: messages.add_error(request, out["errmsg"]) return (False, HttpResponseRedirect(_gen_redirect_postlogin(request)))