Greboca  

Suport technique et veille technologique

Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.

C’est alors la barrière de la prise en main qui fait peur, et pourtant...

Les logiciels libres

L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.

Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.

Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.

Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.

LinuxFr.org : les journaux  -  Django + Jupyter Lab = ❤️

 -  Juillet 2023 - 

Au travail, un de mes juniors data-scientist est arrivé avec une demande toute innocente. "Ça serait bien qu'on puisse avoir un truc style Jupyter Notebook sur la prod pour explorer plus facilement les données".

Notre prod est sous Django, un truc assez commun; et Jupyter aussi a l'air aussi rependu dans le domaine de la data-science. Je me suis dit que quelqu'un avait forcément répondu à la problématique.

Et ben non. Pas du tout.

Il existe bien django-extensions qui permet d'utiliser un projet Django dans des Notebook Jupyter.

Mais je n'ai rien trouvé qui permet d'avoir un Jupyter Notebook intégré dans une application Django. Dommage. J'aurais bien aimé profité de notamment l'authentification Django afin que seul notre staff puisse accéder aux notebooks.

En perdant suffisamment de temps dans les documentations et l'écosystème Jupyter, on fini par découvrir que jupyter-server, le composant propulsant jupyter lab, qui est le futur de jupyter notebook classic, bien que ce dernier soit remplacé par jupyter notebook v7 (‽‽‽) (j'ai RIEN compris à l'écosystème Jupyter, trop le bazar pour moi). Enfin bref, je disais que jupyter-server proposait la possibilité d'utiliser une authentification custom.

TL;DR: Si on implémente la classe IdentityProvider, et plus spécifiquement la méthode get_user(handler), on peut avoir notre propre authentification dans des notebook Jupyter via Django.

Voici un exemple de comment y arriver. Attention, il s'agit d'une implémentation naïve et non un truc propre et efficace. Tout simplement parce que je ne peux pas copier coller le code du taf et que je ne suis pas motivé au point de tout refaire sur mon temps libre.

On part sur un projet Django on ne peux plus standard :

$ mkdir django-notebook
$ cd mkdir django-notebook
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -U pip setuptools wheel && pip install Django django-extensions jupyterlab
$ django-admin startproject proj .
$ ./manage.py migrate
$ ./manage.py createsuperuser

Maintenant, modifer proj/settings.py et ajouter "django_extensions" à la liste INSTALLED_APPS.

Vous pouvez lancer ./manage.py shell_plus --lab pour s'assuser que votre projet Django est bien intégré à Jupyter Lab.

Maintenant, on va créer le fichier proj/jupyter.py est y mettre le code suivant :

# Copyright 2023 Jonathan Tremesaygues
#
# Stronger Beer License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# If you happen to meet one of the copyright holders in a bar you are obligated
# to buy them one pint of beer.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from importlib import import_module
from typing import Awaitable, Optional
from asgiref.sync import sync_to_async
from jupyter_server.auth import IdentityProvider
from jupyter_server.auth.identity import User as JUser
from jupyter_server.base.handlers import JupyterHandler
from django.conf import settings
from django.contrib import auth
from django.contrib.auth.models import User as DUser

# https://jupyter-server.readthedocs.io/en/latest/operators/security.html#jupyter_server.auth.IdentityProvider
class DjangoIdentityProvider(IdentityProvider):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # C'est vraiment la méthode recommandée par Django pour charqer le bon
        # moteur de session >_<
        # https://docs.djangoproject.com/en/4.2/topics/http/sessions/#using-sessions-out-of-views
        self.SessionStore = import_module(settings.SESSION_ENGINE).SessionStore

    # https://jupyter-server.readthedocs.io/en/latest/operators/security.html#jupyter_server.auth.IdentityProvider.get_user
    async def get_user(self, handler: JupyterHandler) -> Optional[JUser] | Awaitable[Optional[JUser]]:
        # `get_user` est utilisé dans un contexte async mais Django préfère un 
        # contexte sync pour parler avec la db. Et le SessionStore par défaut 
        # utilise la db. Pis de toute façon faudra accéder à la DB pour charger
        # l'utilisateur.
        # https://docs.djangoproject.com/en/4.2/topics/async/
        return await sync_to_async(self._get_user)(handler)

    def _get_user(self, handler: JupyterHandler) -> Optional[JUser]:
        # Essaye de récupérer le session id dans les cookies
        if (cookie_entry := handler.request.cookies.get(settings.SESSION_COOKIE_NAME)) is not None:
            # Charge la session correspondante
            session = self.SessionStore(session_key=cookie_entry.value).load()

            # Essaye de récupérer l'user id correspondant
            if (user_id := session[auth.SESSION_KEY]) is not None:
                try:
                    # Essaye de charger l'utilisateur correspondant 
                    user = DUser.objects.get(pk=user_id)
                except DUser.DoesNotExist:
                    # Utilisateur non trouvé
                    pass
                else:
                    # Est-ce que l'utilisateur est un admin?
                    if user.is_staff:
                        # L'utilisateur actuel est bien connecté et est un admin!
                        # Crée un Jupyter user à partir du Django user
                        return JUser(username=user.username)

        # Impossible d'authentifier l'utilisateur
        return None

Relancer le notebook pour qu'il utilise notre classe d'authentification :

$ NOTEBOOK_ARGUMENTS="--ServerApp.identity_provider_class=proj.jupyter.DjangoIdentityProvider --ServerApp.token= --ServerApp.password=" ./manage.py shell_plus --lab

À partir de là, tant que vous ne vous êtes pas authentifié par Django (via votre propre formulaire de connexion ou celui de l'admin de Django), le Notebook vous redirigera vers sa propre page de connexion qui ne sert plus à rien (pareil pour sa page de déconnexion).

Est laissé en exercice au lecteur un code plus propre et optimale. Il faudrait notamment un système de cache afin de ne pas avoir à interroger la base de donnée à CHAQUE requête HTTP afin de savoir si l'utilisateur est connecté.

Est aussi laissé en exercice en lecteur comment intégrer ça dans sa prod. Mais ça marche très bien dans un containeur Docker derrière un reverse proxy. Il faut juste trouver les bonnes options dans jupyterlab --help-all à mettre dans NOTEBOOK_ARGUMENTS.

Pour info, il existe django-read-only. Ce projet permet de mettre un projet Django en lecture seule : impossible de modifier la base de données. Très utile pour s'assurer que personne ne fasse une connerie au travers des notebooks. Il suffit de définir la variable d'environnement DJANGO_READ_ONLY:

DJANGO_READ_ONLY=1 NOTEBOOK_ARGUMENTS=… ./manage.py shell_plus --lab

Commentaires : voir le flux Atom ouvrir dans le navigateur

par jtremesay

LinuxFr.org : les journaux

LinuxFr.org : Journaux

antistress adventure in Flatpak land

 -  30 avril - 

Hello nal, ça faisait un bail !Certain (il se reconnaîtra) m'a demandé de le tenir au courant lorsque j'aurai basculé sur un usage de Firefox (...)


Téléphone sous Linux ?

 -  25 avril - 

Aujourd'hui, avoir un téléphone avec un Android libéré, c'est possible, on pense en particulier à Murena.Avoir un téléphone sous GNU/Linux, c'est (...)


Quand votre voiture vous espionne… et vous le fait payer

 -  23 avril - 

Ceci se passe aux États-Unis, pour l’instant, aucune preuve qu’une telle fuite existe en Europe. Mais… si votre assurance augmente brutalement, (...)


firefox, nouvelle fenêtre dans une session isolée

 -  15 avril - 

Les fenêtres de navigation privées de firefox partagent leurs cookies de session or je souhaitais avoir des fenêtres de navigation isolées, (qui ne (...)


Pretendo tente de déprogrammer l'obsolescence des consoles Nintendo

 -  9 avril - 

Ah Nal,Gros N vient de faire un gros doigt aux utilisateurs de ses consoles 3DS et Wii U en annonçant la fermeture des services en ligne pour (...)