Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
WIP: Flow OIDC correctement implementé coté back
Browse files Browse the repository at this point in the history
Tests à prévoir
  • Loading branch information
ikarius committed Sep 26, 2024
1 parent 3549f51 commit 1d18f95
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 2 deletions.
4 changes: 4 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,14 @@
OIDC_OP_TOKEN_ENDPOINT = f"https://{PC_ISSUER}/token"
# https://fca.integ01.dev-agentconnect.fr/api/v2/userinfo
OIDC_OP_USER_ENDPOINT = f"https://{PC_ISSUER}/userinfo"
OIDC_OP_LOGOUT_ENDPOINT = f"https://{PC_ISSUER}/session/end"

# Intervalle de rafraichissement du token (4h)
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = 4 * 60 * 60

# Redirection vers le front DORA en cas de succès de l'identification
LOGIN_REDIRECT_URL = "/oidc/logged_in"

# Temporaire : modifié pour l'intégration, à supprimer pour la production
OIDC_AUTHENTICATION_CALLBACK_URL = "oidc_authorize_callback"
# Temporaire : force la représentation interne des URL avec un scheme HTTPS (build_absolute_uri)
Expand Down
36 changes: 35 additions & 1 deletion dora/oidc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from logging import getLogger

import requests
from django.core.exceptions import SuspiciousOperation
from mozilla_django_oidc.auth import (
OIDCAuthenticationBackend as MozillaOIDCAuthenticationBackend,
)
from rest_framework.authtoken.models import Token

from dora.users.models import User

logger = getLogger(__name__)


class OIDCError(Exception):
Expand Down Expand Up @@ -56,11 +63,38 @@ def create_user(self, claims):

# L'utilisateur est créé sans mot de passe (aucune connexion à l'admin),
# et comme venant de ProConnect, on considère l'e-mail vérifié.
return self.UserModel.objects.create_user(
new_user = self.UserModel.objects.create_user(
email,
sub_pc=sub,
first_name=claims.get("given_name", "N/D"),
last_name=claims.get("usual_name", "N/D"),
is_valid=True,
)

# compatibilité :
# durant la phase de migration vers ProConnect on ne replace *que* le fournisseur d'identité,
# et on ne touche pas aux mécanismes d'identification entre back et front.
self.get_or_create_drf_token(new_user)

return new_user

def get_user(self, user_id):
if user := super().get_user(user_id):
self.get_or_create_drf_token(user)
return user
return None

def get_or_create_drf_token(self, user_email):
# Pour être temporairement compatible, on crée un token d'identification DRF lié au nouvel utilisateur.
print("get token for:", user_email)
if not user_email:
logger.exception("Utilisateur non renseigné pour la création du token DRF")

user = User.objects.get(email=user_email)

token, created = Token.objects.get_or_create(user=user)

if created:
logger.info("Initialisation du token DRF pour l'utilisateur %s", user_email)

return token
2 changes: 2 additions & 0 deletions dora/oidc/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@
"oidc/authorize", views.oidc_authorize_callback, name="oidc_authorize_callback"
),
path("oidc/login", views.oidc_login, name="oidc_login"),
path("oidc/logged_in", views.oidc_logged_in, name="oidc_logged_in"),
path("oidc/pre_logout", views.oidc_pre_logout, name="oidc_pre_logout"),
]
47 changes: 46 additions & 1 deletion dora/oidc/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import logging
import time

from django.http import HttpResponseForbidden
import jwt
import requests
from django.conf import settings
from django.core.cache import cache
from django.db import transaction
from django.http.response import HttpResponseRedirect
from django.http.response import Http404, HttpResponseRedirect
from django.urls import reverse
from django.utils.crypto import get_random_string
from furl import furl
Expand Down Expand Up @@ -188,6 +189,7 @@ def oidc_authorize_callback(request):
+ f"?{request.META.get("QUERY_STRING")}"
)


@api_view(["GET"])
@permission_classes([permissions.AllowAny])
def oidc_login(request):
Expand All @@ -196,3 +198,46 @@ def oidc_login(request):
redirect_to=reverse("oidc_authentication_init")
+ f"?{request.META.get("QUERY_STRING")}"
)


@api_view(["GET"])
@permission_classes([permissions.AllowAny])
def oidc_logged_in(request):
# redirection vers la page d'accueil de DORA
print("request.user:", request.user)
# attention : l'utilisateur est toujours anonyme (a ce point il n'existe qu'un token DRF)
token = Token.objects.get(user_id=request.session["_auth_user_id"])
print("fetching token...")
return HttpResponseRedirect(
redirect_to=f"{settings.FRONTEND_URL}/auth/pc-callback/{token}"
)


@api_view(["GET"])
@permission_classes([permissions.AllowAny])
def oidc_pre_logout(request):
# attention : le nom oidc_logout est pris par mozilla-django-oidc
# récuperation du token stocké en session:
print("OIDC logout")
if oidc_token := request.session.get("oidc_id_token"):
print("session oidc_token:", oidc_token)
print(
"URL de logout de mozilla-oidc:",
request.build_absolute_uri(reverse("oidc_logout")),
)
# construction de l'URL de logout
# attention au trailing slashes !!!!
params = {
"id_token_hint": oidc_token,
"state": "todo_xxx",
"post_logout_redirect_uri": request.build_absolute_uri(
reverse("oidc_logout").rstrip("/")
),
}
print("logout params:", params)
logout_url = furl(settings.OIDC_OP_LOGOUT_ENDPOINT, args=params)
print("logout URL:", logout_url)
# attention : pas de trailing slash dans la version de test
return HttpResponseRedirect(redirect_to=logout_url.url)
# FIXME: URL de fallback ?
return HttpResponseForbidden("Déconnexion incorrecte")
2 changes: 2 additions & 0 deletions dora/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class User(AbstractBaseUser):
ic_id = models.UUIDField(
verbose_name="Identifiant Inclusion Connect", null=True, blank=True
)

# null possible en base ... pour l'instant
sub_pc = models.UUIDField(verbose_name="Identifiant ProConnect", null=True)

email = models.EmailField(
Expand Down

0 comments on commit 1d18f95

Please sign in to comment.