Skip to content

Commit

Permalink
pro_connect: Add a new SSO
Browse files Browse the repository at this point in the history
  • Loading branch information
tonial committed Oct 5, 2024
1 parent 7ce1ae6 commit 84e48c8
Show file tree
Hide file tree
Showing 41 changed files with 4,056 additions and 131 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG_breaking_changes.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Journal des changements techniques majeurs
## 2024-09-23

- Ajout des variables d'environnement `PRO_CONNECT_*`
la recette ProConnect peut être utilisée en créant une recette jetable
et en suivant les indications de la note bitwarden

## 2024-09-22

- Ajout de la variable d'environnement `API_PARTICULIER_TOKEN` pour appeler l'API en local.
Expand Down
6 changes: 6 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"itou.openid_connect.france_connect",
"itou.openid_connect.inclusion_connect",
"itou.openid_connect.pe_connect",
"itou.openid_connect.pro_connect",
"itou.invitations",
"itou.external_data",
"itou.metabase",
Expand Down Expand Up @@ -417,6 +418,11 @@
INCLUSION_CONNECT_CLIENT_ID = os.getenv("INCLUSION_CONNECT_CLIENT_ID")
INCLUSION_CONNECT_CLIENT_SECRET = os.getenv("INCLUSION_CONNECT_CLIENT_SECRET")

PRO_CONNECT_BASE_URL = os.getenv("PRO_CONNECT_BASE_URL")
PRO_CONNECT_CLIENT_ID = os.getenv("PRO_CONNECT_CLIENT_ID")
PRO_CONNECT_CLIENT_SECRET = os.getenv("PRO_CONNECT_CLIENT_SECRET")
PRO_CONNECT_FT_IDP_HINT = os.getenv("PRO_CONNECT_FT_IDP_HINT")

TALLY_URL = os.getenv("TALLY_URL")

METABASE_HOST = os.getenv("METABASE_HOST")
Expand Down
2 changes: 2 additions & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
path("franceconnect/", include("itou.openid_connect.france_connect.urls")),
# Inclusion Connect URLs.
path("inclusion_connect/", include("itou.openid_connect.inclusion_connect.urls")),
# ProConnect URLs.
path("pro_connect/", include("itou.openid_connect.pro_connect.urls")),
# --------------------------------------------------------------------------------------
# API.
path("api/v1/", include("itou.api.urls", namespace="v1")),
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions itou/openid_connect/pro_connect/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ProConnectConfig(AppConfig):
name = "itou.openid_connect.pro_connect"
verbose_name = "ProConnect"
21 changes: 21 additions & 0 deletions itou/openid_connect/pro_connect/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.conf import settings


# https://github.com/numerique-gouv/agentconnect-documentation/blob/main/doc_fs/donnees_fournies.md
# We should not need to add the email, given_name and usual_name but it doesn"t work without them...
PRO_CONNECT_SCOPES = "openid email given_name usual_name custom"

PRO_CONNECT_CLIENT_ID = settings.PRO_CONNECT_CLIENT_ID
PRO_CONNECT_CLIENT_SECRET = settings.PRO_CONNECT_CLIENT_SECRET

PRO_CONNECT_ENDPOINT_AUTHORIZE = f"{settings.PRO_CONNECT_BASE_URL}/authorize"
PRO_CONNECT_ENDPOINT_TOKEN = f"{settings.PRO_CONNECT_BASE_URL}/token"
PRO_CONNECT_ENDPOINT_USERINFO = f"{settings.PRO_CONNECT_BASE_URL}/userinfo"
PRO_CONNECT_ENDPOINT_LOGOUT = f"{settings.PRO_CONNECT_BASE_URL}/session/end"

# This timeout (in seconds) has been chosen arbitrarily.
PRO_CONNECT_TIMEOUT = 60

PRO_CONNECT_SESSION_KEY = "pro_connect"

PRO_CONNECT_FT_IDP_HINT = settings.PRO_CONNECT_FT_IDP_HINT
10 changes: 10 additions & 0 deletions itou/openid_connect/pro_connect/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import enum


class ProConnectChannel(str, enum.Enum):
"""This enum is stored in the session, and allow us to change the error message
in the callback view depending on where the user came from.
"""

INVITATION = "invitation"
MAP_CONSEILLER = "map_conseiller"
31 changes: 31 additions & 0 deletions itou/openid_connect/pro_connect/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 5.0.3 on 2024-03-22 09:37

import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="ProConnectState",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"created_at",
models.DateTimeField(
db_index=True, default=django.utils.timezone.now, verbose_name="date de création"
),
),
("used_at", models.DateTimeField(null=True, verbose_name="date d'utilisation")),
("data", models.JSONField(blank=True, default=dict, verbose_name="données de session")),
("state", models.CharField(max_length=12, unique=True)),
],
options={
"abstract": False,
},
),
]
Empty file.
63 changes: 63 additions & 0 deletions itou/openid_connect/pro_connect/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import dataclasses
import logging
from typing import ClassVar

from django.db import models

from itou.prescribers.models import PrescriberOrganization
from itou.users.enums import IdentityProvider, UserKind
from itou.users.models import User

from ..models import OIDConnectState, OIDConnectUserData


logger = logging.getLogger(__name__)


class ProConnectState(OIDConnectState):
data = models.JSONField(verbose_name="données de session", default=dict, blank=True)


@dataclasses.dataclass
class ProConnectUserData(OIDConnectUserData):
@staticmethod
def user_info_mapping_dict(user_info: dict):
return {
"username": user_info["sub"],
"first_name": user_info["given_name"],
"last_name": user_info["usual_name"],
"email": user_info["email"],
}

def join_org(self, user: User, safir: str):
if not user.is_prescriber:
raise ValueError("Invalid user kind: %s", user.kind)
try:
organization = PrescriberOrganization.objects.get(code_safir_pole_emploi=safir)
except PrescriberOrganization.DoesNotExist:
logger.error(f"Organization with SAFIR {safir} does not exist. Unable to add user {user.email}.")
raise
if not organization.has_member(user):
organization.add_or_activate_member(user)


@dataclasses.dataclass
class ProConnectPrescriberData(ProConnectUserData):
kind: UserKind = UserKind.PRESCRIBER
identity_provider: IdentityProvider = IdentityProvider.PRO_CONNECT
login_allowed_user_kinds: ClassVar[tuple[UserKind]] = (UserKind.PRESCRIBER, UserKind.EMPLOYER)
allowed_identity_provider_migration: ClassVar[tuple[IdentityProvider]] = (
IdentityProvider.DJANGO,
IdentityProvider.INCLUSION_CONNECT,
)


@dataclasses.dataclass
class ProConnectEmployerData(ProConnectUserData):
kind: UserKind = UserKind.EMPLOYER
identity_provider: IdentityProvider = IdentityProvider.PRO_CONNECT
login_allowed_user_kinds: ClassVar[tuple[UserKind]] = (UserKind.PRESCRIBER, UserKind.EMPLOYER)
allowed_identity_provider_migration: ClassVar[tuple[IdentityProvider]] = (
IdentityProvider.DJANGO,
IdentityProvider.INCLUSION_CONNECT,
)
13 changes: 13 additions & 0 deletions itou/openid_connect/pro_connect/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.urls import path

from . import views


app_name = "pro_connect"

urlpatterns = [
path("authorize", views.pro_connect_authorize, name="authorize"),
path("callback", views.pro_connect_callback, name="callback"),
path("logout", views.pro_connect_logout, name="logout"),
path("logout_callback", views.pro_connect_logout_callback, name="logout_callback"),
]
Loading

0 comments on commit 84e48c8

Please sign in to comment.